diff --git a/docs/en/Tutorials/Angular/Part-I.md b/docs/en/Tutorials/Angular/Part-I.md index 06ba6aa41d..5b8bb551c1 100644 --- a/docs/en/Tutorials/Angular/Part-I.md +++ b/docs/en/Tutorials/Angular/Part-I.md @@ -308,4 +308,399 @@ Swagger has a nice UI to test APIs. You can try to execute the `[GET] /api/app/b ### Create the Books Page -It's time to create something visible and usable! \ No newline at end of file +It's time to create something visible and usable! + + + +In this Angular Application, [Angular CLI](https://angular.io/cli) will be used to creating modules, components, services, etc. , [NGXS](https://ngxs.gitbook.io/ngxs/) will be used as state management and [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) will be used as the UI library. + + +Open a terminal window and go to `angular` folder and then run `yarn` command for installing packages. + +Run the following command line for creating `BooksModule` + +```bash +yarn ng generate module books --route books --module app.module +``` + +![creating-books-module.terminal](images/creating-books-module-terminal.png) + +Run `yarn start` and then open `http://localhost:4200/books` on a browser. + +![initial-books-page](images/initial-books-page.png) + +Books page works but you need some configuration for the application layout. + +Let's start the coding. + +>[Visual Studio Code](https://code.visualstudio.com/) will be used in this tutorial. + +Open the `app-routing.module.ts` and replace `books` route to the below route. + +```typescript +import { LayoutApplicationComponent } from '@abp/ng.theme.basic';- + +//... +{ + path: 'books', + component: LayoutApplicationComponent, + loadChildren: () => import('./books/books.module').then(m => m.BooksModule), + data: { + routes: { + name: 'Books', + } as ABP.Route, + }, +}, +``` + +If you would like to see your route on the navigation bar of `LayoutApplication`, you must add the `data` object with `name` property in your route. + +>ABP themes have three layouts. These layouts are `LayoutApplication`, `LayoutAccount` and `LayoutEmpty`. [Check these layouts.](https://github.com/abpframework/abp/tree/dev/npm/ng-packs/packages/theme-basic/src/lib/components) + + +![initial-books-page](images/initial-books-page-with-layout.png) + +`LayoutApplication` successfully added and `Books` successfully added to the navigation bar. + +Let's create the `book-list.component`. + + +Replace the `books.component.html` to the following line + +```html + +``` + +Run the below command on the terminal in the root folder. + +`yarn ng generate component books/book-list` + + +![creating-books-list-terminal](images/creating-book-list-terminal.png) + +Import the `SharedModule` to the `BooksModule` + +```typescript +import { SharedModule } from '../shared/shared.module'; + +@NgModule({ + //... + imports: [ + //... + SharedModule + ], +}) +export class BooksModule {} +``` + +After, update to `routes` in `books-routing.module.ts` + +```typescript +import { BookListComponent } from './book-list/book-list.component'; + +const routes: Routes = [ + { + path: '', + component: BooksComponent, + children: [{ path: '', component: BookListComponent }], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class BooksRoutingModule {} +``` + +![initial-book-list-page](images/initial-book-list-page.png) + +It seems good. + +Now, you created `BookListComponent` and added in the `BooksRoutingModule`. + + + +

Create the Books State

+ +In this step, [NGXS Schematic](https://github.com/mehmet-erim/ngxs-schematic) will be used to generate state. + +Run the next command on terminal. + +`yarn ng generate ngxs-schematic:state books` + +![ngxs-schematic-terminal-output](images/ngxs-schematic-terminal-output.png) + +```typescript +// books.ts + +export namespace Books { + export interface State { + books: any; + } +} +``` + +```typescript +// books.actions.ts + +export class BooksAction { + static readonly type = '[Books] Action'; + constructor(public payload?: any) { } +} +``` +>See [Actions on NGXS Document](https://ngxs.gitbook.io/ngxs/concepts/actions) + +```typescript +// books.state.ts + +import { State, Action, StateContext, Selector } from '@ngxs/store'; +import { BooksAction } from '../actions/books.actions'; +import { Books } from '../models/books'; + +@State ({ + name: 'BooksState', + defaults: { books: {} } as Books.State +}) +export class BooksState { + @Selector() + static getBooks({ books }: Books.State) { + return books; + } + + constructor() { } + + @Action(BooksAction) + booksAction({ getState, patchState }: StateContext, { payload }: BooksAction) { + const state = getState(); + patchState({ + ...state, + }); + } +} +``` +>See [State on NGXS Document](https://ngxs.gitbook.io/ngxs/concepts/state) + +NGXS Schematic created the above files and added `BooksState` to `NgxsModule` in `app.modules.ts`. + +```typescript +// app.module.ts + +import { BooksState } from './store/states/books.state'; + +@NgModule({ + imports: [ + //... + NgxsModule.forRoot([BooksState]), + ], + //... +}) +export class AppModule {} +``` + +

Get Books Data from Backend

+ +Firstly, you have to create the response type of book API. +>Book response type must be the same as the backend response type. You can use swagger for response types. + +Add the below types to `books.ts` + +```typescript +import { ABP } from '@abp/ng.core'; + +export namespace Books { + export interface State { + books: Response; + } + + export type Response = ABP.PagedResponse; + + export interface Book { + name: string; + type: Type; + publishDate: string; + price: number; + lastModificationTime: string; + lastModifierId: string; + creationTime: string; + creatorId: string; + id: string; + } + + export enum Type { + Undefined, + Adventure, + Biography, + Dystopia, + Fantastic, + Horror, + Science, + ScienceFiction, + Poetry, + } +} +``` + +OK, you ready to create a service. + +```bash +yarn ng generate service books/shared/books +``` + +![service-terminal-output](images/service-terminal-output.png) + +Add a get method to this service. + +```typescript +import { Injectable } from '@angular/core'; +import { RestService, Rest } from '@abp/ng.core'; +import { Books } from '../../store/models'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class BooksService { + constructor(private rest: RestService) {} + + get(): Observable { + const request: Rest.Request = { + method: 'GET', + url: '/api/app/book', + }; + + return this.rest.request(request); + } +} +``` + +Replace the following code block to `books.actions.ts`. + +```typescript +export class GetBooks { + static readonly type = '[Books] Get'; +} +``` + +Replace the next code block to `books.state.ts`. + +```typescript +import { State, Action, StateContext, Selector } from '@ngxs/store'; +import { GetBooks } from '../actions/books.actions'; +import { Books } from '../models/books'; +import { BooksService } from '../../books/shared/books.service'; +import { tap } from 'rxjs/operators'; + +@State({ + name: 'BooksState', + defaults: { books: {} } as Books.State, +}) +export class BooksState { + @Selector() + static getBooks({ books }: Books.State) { + return books.items || []; + } + + constructor(private booksService: BooksService) {} + + @Action(GetBooks) + get({ patchState }: StateContext) { + /* Suggestion: + You never subscribe to an observable here + and don't forget to return + */ + return this.booksService.get().pipe( + tap(books => { + patchState({ + books, + }); + }), + ); + } +} + +``` +>See the [Selectors on NGXS Document](https://ngxs.gitbook.io/ngxs/concepts/select#memoized-selectors) + +You ready to get books and list in the table. + +```typescript +import { Component, OnInit } from '@angular/core'; +import { Store, Select } from '@ngxs/store'; +import { BooksState } from '../../store/states'; +import { Observable } from 'rxjs'; +import { Books } from '../../store/models'; +import { GetBooks } from '../../store/actions'; + +@Component({ + selector: 'app-book-list', + templateUrl: './book-list.component.html', + styleUrls: ['./book-list.component.scss'], +}) +export class BookListComponent implements OnInit { + @Select(BooksState.getBooks) + books$: Observable; + + booksType = Books.Type; + + loading = false; + + constructor(private store: Store) {} + + ngOnInit() { + this.loading = true; + this.store.dispatch(new GetBooks()) + .subscribe(() => { + // This subscribe block runs when the action completed successfully + this.loading = false + }; + } +} +``` + +>See the [Dispatching Actions](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions) and [Select](https://ngxs.gitbook.io/ngxs/concepts/select) on NGXS Document + +Copy next code block to `book-list.component.html`. + +```html +
+
+
+
+
+ Books +
+
+
+
+
+ + + + Book name + Book type + Publish date + Price + + + + + {{ data.name }} + {{ booksType[data.type] }} + {{ data.publishDate | date }} + {{ data.price }} + + + +
+
+``` +> PrimeNG Table used in this component. See the [PrimeNG Table Document](https://www.primefaces.org/primeng/#/table) + +The final UI is shown below: + +![bookstore-book-list](images/bookstore-book-list.png) + +### Next Part + +See the [next part](Part-II.md) of this tutorial. diff --git a/docs/en/Tutorials/Angular/images/bookstore-book-list.png b/docs/en/Tutorials/Angular/images/bookstore-book-list.png new file mode 100644 index 0000000000..80718e7c52 Binary files /dev/null and b/docs/en/Tutorials/Angular/images/bookstore-book-list.png differ diff --git a/docs/en/Tutorials/Angular/images/creating-book-list-terminal.png b/docs/en/Tutorials/Angular/images/creating-book-list-terminal.png new file mode 100644 index 0000000000..9f01e94121 Binary files /dev/null and b/docs/en/Tutorials/Angular/images/creating-book-list-terminal.png differ diff --git a/docs/en/Tutorials/Angular/images/creating-books-module-terminal.png b/docs/en/Tutorials/Angular/images/creating-books-module-terminal.png new file mode 100644 index 0000000000..c74b37b1b5 Binary files /dev/null and b/docs/en/Tutorials/Angular/images/creating-books-module-terminal.png differ diff --git a/docs/en/Tutorials/Angular/images/initial-book-list-page.png b/docs/en/Tutorials/Angular/images/initial-book-list-page.png new file mode 100644 index 0000000000..2a42cabb38 Binary files /dev/null and b/docs/en/Tutorials/Angular/images/initial-book-list-page.png differ diff --git a/docs/en/Tutorials/Angular/images/initial-books-page-with-layout.png b/docs/en/Tutorials/Angular/images/initial-books-page-with-layout.png new file mode 100644 index 0000000000..ddd0913493 Binary files /dev/null and b/docs/en/Tutorials/Angular/images/initial-books-page-with-layout.png differ diff --git a/docs/en/Tutorials/Angular/images/initial-books-page.png b/docs/en/Tutorials/Angular/images/initial-books-page.png new file mode 100644 index 0000000000..6d6696d366 Binary files /dev/null and b/docs/en/Tutorials/Angular/images/initial-books-page.png differ diff --git a/docs/en/Tutorials/Angular/images/ngxs-schematic-terminal-output.png b/docs/en/Tutorials/Angular/images/ngxs-schematic-terminal-output.png new file mode 100644 index 0000000000..3dca358bdc Binary files /dev/null and b/docs/en/Tutorials/Angular/images/ngxs-schematic-terminal-output.png differ diff --git a/docs/en/Tutorials/Angular/images/service-terminal-output.png b/docs/en/Tutorials/Angular/images/service-terminal-output.png new file mode 100644 index 0000000000..69aaccba31 Binary files /dev/null and b/docs/en/Tutorials/Angular/images/service-terminal-output.png differ