diff --git a/docs/en/Tutorials/Part-10.md b/docs/en/Tutorials/Part-10.md index 5feacf99f4..886faf4bf1 100644 --- a/docs/en/Tutorials/Part-10.md +++ b/docs/en/Tutorials/Part-10.md @@ -923,6 +923,149 @@ You can run the application and try to create a new book or update an existing b {{else if UI=="NG"}} -***Angular UI is being prepared...*** +### The Book List + +Book list page change is trivial. Open the `/src/app/book/book.component.html` and add the following column definition between the `Name` and `Type` columns: + +````js + +```` + +When you run the application, you can see the *Author* column on the table: + +![bookstore-books-with-authorname-angular](images/bookstore-books-with-authorname-angular.png) + +### Create/Edit Forms + +The next step is to add an Author selection (dropdown) to the create/edit forms. The final UI will look like the one shown below: + +![bookstore-angular-author-selection](images/bookstore-angular-author-selection.png) + +Added the Author dropdown as the first element in the form. + +Open the `/src/app/book/book.component.ts` and and change the content as shown below: + +````typescript +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookService, BookDto, bookTypeOptions, AuthorLookupDto } from '@proxy/books'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; +import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], +}) +export class BookComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; + + form: FormGroup; + + selectedBook = {} as BookDto; + + authors$: Observable; + + bookTypes = bookTypeOptions; + + isModalOpen = false; + + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder, + private confirmation: ConfirmationService + ) { + this.authors$ = bookService.getAuthorLookup().pipe(map((r) => r.items)); + } + + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getList(query); + + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); + } + + createBook() { + this.selectedBook = {} as BookDto; + this.buildForm(); + this.isModalOpen = true; + } + + editBook(id: string) { + this.bookService.get(id).subscribe((book) => { + this.selectedBook = book; + this.buildForm(); + this.isModalOpen = true; + }); + } + + buildForm() { + this.form = this.fb.group({ + authorId: [this.selectedBook.authorId || null, Validators.required], + name: [this.selectedBook.name || null, Validators.required], + type: [this.selectedBook.type || null, Validators.required], + publishDate: [ + this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null, + Validators.required, + ], + price: [this.selectedBook.price || null, Validators.required], + }); + } + + save() { + if (this.form.invalid) { + return; + } + + const request = this.selectedBook.id + ? this.bookService.update(this.selectedBook.id, this.form.value) + : this.bookService.create(this.form.value); + + request.subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); + } + + delete(id: string) { + this.confirmation.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure').subscribe((status) => { + if (status === Confirmation.Status.confirm) { + this.bookService.delete(id).subscribe(() => this.list.get()); + } + }); + } +} +```` + +* Added imports for the `AuthorLookupDto`, `Observable` and `map`. +* Added `authors$: Observable;` field after the `selectedBook`. +* Added `this.authors$ = bookService.getAuthorLookup().pipe(map((r) => r.items));` into the constructor. +* Added ` authorId: [this.selectedBook.authorId || null, Validators.required],` into the `buildForm()` function. + +Open the `/src/app/book/book.component.html` and add the following form group just before the book name form group: + +````html +
+ * + +
+```` + +That's all. Just run the application and try to create or edit an author. {{end}} \ No newline at end of file diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index c947f4495d..bcec504886 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -347,9 +347,9 @@ This is a fully working, server side paged, sorted and localized table of books. ## Install NPM packages -> Notice: This tutorial is based on the ABP Framework v3.0.3+ If your project version is older, then please upgrade your solution. See the [migration guide](../UI/Angular/Migration-Guide-v3.md) if you are upgrading an existing project with v2.x. +> Notice: This tutorial is based on the ABP Framework v3.1.0+ If your project version is older, then please upgrade your solution. See the [migration guide](../UI/Angular/Migration-Guide-v3.md) if you are upgrading an existing project with v2.x. -If you haven't done it before, open a new command line interface (terminal window) and go to your `angular` folder and then run `yarn` command to install NPM packages: +If you haven't done it before, open a new command line interface (terminal window) and go to your `angular` folder and then run `yarn` command to install the NPM packages: ```bash yarn @@ -473,9 +473,9 @@ Run the following command in the `angular` folder: abp generate-proxy ``` -The generated files looks like below: +This command will create the following files under the `/src/app/proxy/books` folder: -![Generated files](./images/generated-proxies-2.png) +![Generated files](./images/generated-proxies-3.png) ### BookComponent @@ -484,8 +484,7 @@ Open the `/src/app/book/book.component.ts` file and replace the content as below ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto } from './models'; -import { BookService } from './services'; +import { BookService, BookDto } from '@proxy/books'; @Component({ selector: 'app-book', @@ -499,7 +498,7 @@ export class BookComponent implements OnInit { constructor(public readonly list: ListService, private bookService: BookService) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; diff --git a/docs/en/Tutorials/Part-3.md b/docs/en/Tutorials/Part-3.md index 13aa5df8c3..be3dd75d64 100644 --- a/docs/en/Tutorials/Part-3.md +++ b/docs/en/Tutorials/Part-3.md @@ -656,8 +656,7 @@ Open `/src/app/book/book.component.ts` and replace the content as below: ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto } from './models'; -import { BookService } from './services'; +import { BookService, BookDto } from '@proxy/books'; @Component({ selector: 'app-book', @@ -673,7 +672,7 @@ export class BookComponent implements OnInit { constructor(public readonly list: ListService, private bookService: BookService) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; @@ -749,8 +748,8 @@ Open `/src/app/book/book.component.ts` and replace the content as below: ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from './models'; // add BookType -import { BookService } from './services'; +// import bookTypeOptions from @proxy/books +import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this @Component({ @@ -764,12 +763,8 @@ export class BookComponent implements OnInit { form: FormGroup; // add this line - bookType = BookType; // add this line - // add bookTypes as a list of BookType enum members - bookTypes = Object.keys(this.bookType).filter( - (key) => typeof this.bookType[key] === 'number' - ); + bookTypes = bookTypeOptions; isModalOpen = false; @@ -780,7 +775,7 @@ export class BookComponent implements OnInit { ) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; @@ -808,7 +803,7 @@ export class BookComponent implements OnInit { return; } - this.bookService.createByInput(this.form.value).subscribe(() => { + this.bookService.create(this.form.value).subscribe(() => { this.isModalOpen = false; this.form.reset(); this.list.get(); @@ -819,7 +814,6 @@ export class BookComponent implements OnInit { * Imported `FormGroup`, `FormBuilder` and `Validators` from `@angular/forms`. * Added `form: FormGroup` property. -* Added `bookType` property so that you can reach `BookType` enum members from template. * Added `bookTypes` property as a list of `BookType` enum members. That will be used in form options. * Injected `FormBuilder` into the constructor. [FormBuilder](https://angular.io/api/forms/FormBuilder) provides convenient methods for generating form controls. It reduces the amount of boilerplate needed to build complex forms. * Added `buildForm` method to the end of the file and executed the `buildForm()` in the `createBook` method. @@ -844,7 +838,7 @@ Open `/src/app/book/book.component.html` and replace ` Type * @@ -910,8 +904,7 @@ Open `/src/app/book/book.component.ts` and replace the content as below: ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from './models'; -import { BookService } from './services'; +import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // added this line @@ -931,11 +924,7 @@ export class BookComponent implements OnInit { form: FormGroup; - bookType = BookType; - - bookTypes = Object.keys(this.bookType).filter( - (key) => typeof this.bookType[key] === 'number' - ); + bookTypes = bookTypeOptions; isModalOpen = false; @@ -946,7 +935,7 @@ export class BookComponent implements OnInit { ) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; @@ -972,7 +961,7 @@ export class BookComponent implements OnInit { return; } - this.bookService.createByInput(this.form.value).subscribe(() => { + this.bookService.create(this.form.value).subscribe(() => { this.isModalOpen = false; this.form.reset(); this.list.get(); @@ -995,8 +984,7 @@ Open `/src/app/book/book.component.ts` and replace the content as shown below: ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType, CreateUpdateBookDto } from './models'; -import { BookService } from './services'; +import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; @@ -1009,15 +997,11 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap export class BookComponent implements OnInit { book = { items: [], totalCount: 0 } as PagedResultDto; - selectedBook = new BookDto(); // declare selectedBook + selectedBook = {} as BookDto; // declare selectedBook form: FormGroup; - bookType = BookType; - - bookTypes = Object.keys(this.bookType).filter( - (key) => typeof this.bookType[key] === 'number' - ); + bookTypes = bookTypeOptions; isModalOpen = false; @@ -1028,7 +1012,7 @@ export class BookComponent implements OnInit { ) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; @@ -1036,14 +1020,14 @@ export class BookComponent implements OnInit { } createBook() { - this.selectedBook = new BookDto(); // reset the selected book + this.selectedBook = {} as BookDto; // reset the selected book this.buildForm(); this.isModalOpen = true; } // Add editBook method editBook(id: string) { - this.bookService.getById(id).subscribe((book) => { + this.bookService.get(id).subscribe((book) => { this.selectedBook = book; this.buildForm(); this.isModalOpen = true; @@ -1069,8 +1053,8 @@ export class BookComponent implements OnInit { } const request = this.selectedBook.id - ? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id) - : this.bookService.createByInput(this.form.value); + ? this.bookService.update(this.selectedBook.id, this.form.value) + : this.bookService.create(this.form.value); request.subscribe(() => { this.isModalOpen = false; @@ -1155,7 +1139,7 @@ constructor( delete(id: string) { this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => { if (status === Confirmation.Status.confirm) { - this.bookService.deleteById(id).subscribe(() => this.list.get()); + this.bookService.delete(id).subscribe(() => this.list.get()); } }); } diff --git a/docs/en/Tutorials/Part-9.md b/docs/en/Tutorials/Part-9.md index 1b9451700d..0d78d584b8 100644 --- a/docs/en/Tutorials/Part-9.md +++ b/docs/en/Tutorials/Part-9.md @@ -621,7 +621,7 @@ abp generate-proxy This command generates the service proxy for the author service and the related model (DTO) classes: -![bookstore-angular-service-proxy-author](images/bookstore-angular-service-proxy-author.png) +![bookstore-angular-service-proxy-author](images/bookstore-angular-service-proxy-author-2.png) ### AuthorComponent @@ -630,8 +630,7 @@ Open the `/src/app/author/author.component.ts` file and replace the content as b ```js import { Component, OnInit } from '@angular/core'; import { ListService, PagedResultDto } from '@abp/ng.core'; -import { AuthorDto } from './models'; -import { AuthorService } from './services'; +import { AuthorService, AuthorDto } from '@proxy/authors'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; @@ -649,7 +648,7 @@ export class AuthorComponent implements OnInit { form: FormGroup; - selectedAuthor = new AuthorDto(); + selectedAuthor = {} as AuthorDto; constructor( public readonly list: ListService, @@ -659,7 +658,7 @@ export class AuthorComponent implements OnInit { ) {} ngOnInit(): void { - const authorStreamCreator = (query) => this.authorService.getListByInput(query); + const authorStreamCreator = (query) => this.authorService.getList(query); this.list.hookToQuery(authorStreamCreator).subscribe((response) => { this.author = response; @@ -667,13 +666,13 @@ export class AuthorComponent implements OnInit { } createAuthor() { - this.selectedAuthor = new AuthorDto(); + this.selectedAuthor = {} as AuthorDto; this.buildForm(); this.isModalOpen = true; } editAuthor(id: string) { - this.authorService.getById(id).subscribe((author) => { + this.authorService.get(id).subscribe((author) => { this.selectedAuthor = author; this.buildForm(); this.isModalOpen = true; @@ -697,14 +696,14 @@ export class AuthorComponent implements OnInit { if (this.selectedAuthor.id) { this.authorService - .updateByIdAndInput(this.form.value, this.selectedAuthor.id) + .update(this.selectedAuthor.id, this.form.value) .subscribe(() => { this.isModalOpen = false; this.form.reset(); this.list.get(); }); } else { - this.authorService.createByInput(this.form.value).subscribe(() => { + this.authorService.create(this.form.value).subscribe(() => { this.isModalOpen = false; this.form.reset(); this.list.get(); @@ -716,7 +715,7 @@ export class AuthorComponent implements OnInit { this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure') .subscribe((status) => { if (status === Confirmation.Status.confirm) { - this.authorService.deleteById(id).subscribe(() => this.list.get()); + this.authorService.delete(id).subscribe(() => this.list.get()); } }); } diff --git a/docs/en/Tutorials/images/bookstore-angular-author-selection.png b/docs/en/Tutorials/images/bookstore-angular-author-selection.png new file mode 100644 index 0000000000..bb604233ab Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-angular-author-selection.png differ diff --git a/docs/en/Tutorials/images/bookstore-angular-service-proxy-author-2.png b/docs/en/Tutorials/images/bookstore-angular-service-proxy-author-2.png new file mode 100644 index 0000000000..d76179bb78 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-angular-service-proxy-author-2.png differ diff --git a/docs/en/Tutorials/images/bookstore-books-with-authorname-angular.png b/docs/en/Tutorials/images/bookstore-books-with-authorname-angular.png new file mode 100644 index 0000000000..6f081c16f9 Binary files /dev/null and b/docs/en/Tutorials/images/bookstore-books-with-authorname-angular.png differ diff --git a/docs/en/Tutorials/images/generated-proxies-3.png b/docs/en/Tutorials/images/generated-proxies-3.png new file mode 100644 index 0000000000..2a94f4e389 Binary files /dev/null and b/docs/en/Tutorials/images/generated-proxies-3.png differ