Browse Source

docs: update bookstore tutorial

resolves #3504
pull/3734/head
mehmet-erim 6 years ago
parent
commit
5e4fab7c9c
  1. 143
      docs/en/Tutorials/Part-1.md
  2. 495
      docs/en/Tutorials/Part-2.md
  3. BIN
      docs/en/Tutorials/images/generate-proxy-command.png
  4. BIN
      docs/en/Tutorials/images/generated-proxies.png
  5. 1
      templates/app/angular/package.json

143
docs/en/Tutorials/Part-1.md

@ -880,7 +880,7 @@ Run the following command in the terminal to create a new state, named `BooksSta
npx @ngxs/cli --name books --directory src/app/books
```
* This command creates books.state.ts and books.actions.ts files in the `src/app/books/state` folder. See the [NGXS CLI documentation](https://www.ngxs.io/plugins/cli).
* This command creates `books.state.ts` and `books.actions.ts` files in the `src/app/books/state` folder. See the [NGXS CLI documentation](https://www.ngxs.io/plugins/cli).
Import the `BooksState` to the `app.module.ts` in the `src/app` folder and then add the `BooksState` to `forRoot` static method of `NgxsModule` as an array element of the first parameter of the method.
@ -901,89 +901,27 @@ import { BooksState } from './books/state/books.state'; //<== imported BooksStat
export class AppModule {}
```
#### Get books data from backend
#### Generate proxies
Create data types to map the data from the backend (you can check Swagger UI or your backend API to see the data format).
ABP CLI provides `generate-proxy` command that generates client proxies for your HTTP APIs to make easy to consume your services from the client side. Before running generate-proxy command, your host must be up and running. See the [CLI documentation](../CLI.md)
![BookDto properties](./images/bookstore-swagger-book-dto-properties.png)
Open the `books.ts` file in the `app\store\models` folder and replace the content as below:
```js
export namespace Books {
export interface State {
books: Response;
}
export interface Response {
items: Book[];
totalCount: number;
}
export interface Book {
name: string;
type: BookType;
publishDate: string;
price: number;
lastModificationTime: string;
lastModifierId: string;
creationTime: string;
creatorId: string;
id: string;
}
export enum BookType {
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry,
}
}
```
* Added `Book` interface that represents a book object and `BookType` enum which represents a book category.
#### BooksService
Create a new service, named `BooksService` to perform `HTTP` calls to the server:
Run the following command in the `angular` folder:
```bash
yarn ng generate service books/shared/books
abp generate-proxy --module app
```
![service-terminal-output](./images/bookstore-service-terminal-output.png)
![Generate proxy command](./images/generate-proxy-command.png)
Open the `books.service.ts` file in `app\books\shared` folder and replace the content as below:
The generated files looks like below:
```js
import { Injectable } from '@angular/core';
import { RestService } from '@abp/ng.core';
import { Books } from '../../store/models';
import { Observable } from 'rxjs';
![Generated files](./images/generated-proxies.png)
@Injectable({
providedIn: 'root',
})
export class BooksService {
constructor(private restService: RestService) {}
get(): Observable<Books.Response> {
return this.restService.request<void, Books.Response>({
method: 'GET',
url: '/api/app/book'
});
}
}
```
#### GetBooks Action
* We added the `get` method to get the list of books by performing an HTTP request to the related endpoint.
Actions can either be thought of as a command which should trigger something to happen, or as the resulting event of something that has already happened. [See NGXS Actions documentation](https://www.ngxs.io/concepts/actions).
Open the`books.actions.ts` file in `app\store\actions` folder and replace the content below:
Open the `books.actions.ts` file in `app/books/state` folder and replace the content below:
```js
export class GetBooks {
@ -993,43 +931,48 @@ export class GetBooks {
#### Implement BooksState
Open the `books.state.ts` file in `app\store\states` folder and replace the content below:
Open the `books.state.ts` file in `app/books/state` folder and replace the content below:
```js
import { PagedResultDto } from '@abp/ng.core';
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 { GetBooks } from './books.actions';
import { BookService } from '../../app/shared/services';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../../app/shared/models';
@State<Books.State>({
export class BooksStateModel {
public book: PagedResultDto<BookDto>;
}
@State<BooksStateModel>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
defaults: { book: {} } as BooksStateModel,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: Books.State) {
return state.books.items || [];
static getBooks(state: BooksStateModel) {
return state.book.items || [];
}
constructor(private booksService: BooksService) {}
constructor(private bookService: BookService) {}
@Action(GetBooks)
get(ctx: StateContext<Books.State>) {
return this.booksService.get().pipe(
tap(booksResponse => {
get(ctx: StateContext<BooksStateModel>) {
return this.bookService.getListByInput().pipe(
tap((booksResponse) => {
ctx.patchState({
books: booksResponse,
book: booksResponse,
});
}),
})
);
}
}
```
* We added the `GetBooks` action that retrieves the books data via `BooksService` and patches the state.
* We added the book property to BooksStateModel model.
* We added the `GetBooks` action that retrieves the books data via `BooksService` that generated via ABP CLI and patches the state.
* `NGXS` requires to return the observable without subscribing it in the get function.
#### BookListComponent
@ -1038,11 +981,12 @@ Open the `book-list.component.ts` file in `app\books\book-list` folder and repla
```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
@Component({
selector: 'app-book-list',
@ -1051,13 +995,13 @@ import { GetBooks } from '../../store/actions';
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<Books.Book[]>;
books$: Observable<BookDto[]>;
booksType = Books.BookType;
booksType = BookType;
loading = false;
constructor(private store: Store) { }
constructor(private store: Store) {}
ngOnInit() {
this.get();
@ -1065,9 +1009,10 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
}
}
```

495
docs/en/Tutorials/Part-2.md

@ -456,161 +456,76 @@ Run the application and try to delete a book.
In this section, you will learn how to create a new modal dialog form to create a new book.
#### Type definition
Open `books.ts` file in `app\store\models` folder and replace the content as below:
```js
export namespace Books {
export interface State {
books: Response;
}
export interface Response {
items: Book[];
totalCount: number;
}
export interface Book {
name: string;
type: BookType;
publishDate: string;
price: number;
lastModificationTime: string;
lastModifierId: string;
creationTime: string;
creatorId: string;
id: string;
}
export enum BookType {
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry,
}
//<== added CreateUpdateBookInput interface ==>
export interface CreateUpdateBookInput {
name: string;
type: BookType;
publishDate: string;
price: number;
}
}
```
* We added `CreateUpdateBookInput` interface.
* You can see the properties of this interface from Swagger UI.
* The `CreateUpdateBookInput` interface matches with the `CreateUpdateBookDto` in the backend.
#### Service method
Open the `books.service.ts` file in `app\books\shared` folder and replace the content as below:
```js
import { Injectable } from '@angular/core';
import { RestService } from '@abp/ng.core';
import { Books } from '../../store/models';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class BooksService {
constructor(private restService: RestService) {}
get(): Observable<Books.Response> {
return this.restService.request<void, Books.Response>({
method: 'GET',
url: '/api/app/book'
});
}
//<== added create method ==>
create(createBookInput: Books.CreateUpdateBookInput): Observable<Books.Book> {
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'POST',
url: '/api/app/book',
body: createBookInput
});
}
}
```
- We added the `create` method to perform an HTTP Post request to the server.
- `restService.request` function gets generic parameters for the types sent to and received from the server. This example sends a `CreateUpdateBookInput` object and receives a `Book` object (you can set `void` for request or return type if not used).
#### State definitions
Open `books.action.ts` in `app\store\actions` folder and replace the content as below:
Open `books.action.ts` in `books\state` folder and replace the content as below:
```js
import { Books } from '../models'; //<== added this line ==>
import { CreateUpdateBookDto } from '../../app/shared/models'; //<== added this line ==>
export class GetBooks {
static readonly type = '[Books] Get';
}
//added CreateUpdateBook class
// added CreateUpdateBook class
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: Books.CreateUpdateBookInput) { }
constructor(public payload: CreateUpdateBookDto) { }
}
```
* We imported the Books namespace and created the `CreateUpdateBook` action.
* We imported the `CreateUpdateBookDto` model and created the `CreateUpdateBook` action.
Open `books.state.ts` file in `app\store\states` and replace the content as below:
Open `books.state.ts` file in `books\state` folder and replace the content as below:
```js
import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks, CreateUpdateBook } from '../actions/books.actions'; //<== added CreateUpdateBook==>
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { GetBooks, CreateUpdateBook } from './books.actions'; // <== added CreateUpdateBook==>
import { BookService } from '../../app/shared/services';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../../app/shared/models';
export class BooksStateModel {
public book: PagedResultDto<BookDto>;
}
@State<Books.State>({
@State<BooksStateModel>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
defaults: { book: {} } as BooksStateModel,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: Books.State) {
return state.books.items || [];
static getBooks(state: BooksStateModel) {
return state.book.items || [];
}
constructor(private booksService: BooksService) { }
constructor(private bookService: BookService) {}
@Action(GetBooks)
get(ctx: StateContext<Books.State>) {
return this.booksService.get().pipe(
tap(booksResponse => {
get(ctx: StateContext<BooksStateModel>) {
return this.bookService.getListByInput().pipe(
tap((bookResponse) => {
ctx.patchState({
books: booksResponse,
book: bookResponse,
});
}),
})
);
}
//added CreateUpdateBook action listener
// added CreateUpdateBook action listener
@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
return this.booksService.create(action.payload);
save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
return this.bookService.createByInput(action.payload);
}
}
```
* We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book.
When the `SaveBook` action dispatched, the save method is being executed. It calls `create` method of the `BooksService`.
When the `SaveBook` action dispatched, the save method is being executed. It calls `create` method of the `BookService`.
#### Add a modal to BookListComponent
@ -694,11 +609,12 @@ Open `book-list.component.ts` file in `books\book-list` folder and replace the c
```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
@Component({
selector: 'app-book-list',
@ -707,15 +623,15 @@ import { GetBooks } from '../../store/actions';
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<Books.Book[]>;
books$: Observable<BookDto[]>;
booksType = Books.BookType;
booksType = BookType;
loading = false;
isModalOpen = false; //<== added this line ==>
isModalOpen = false; // <== added this line ==>
constructor(private store: Store) { }
constructor(private store: Store) {}
ngOnInit() {
this.get();
@ -723,12 +639,13 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
}
//added createBook method
// added createBook method
createBook() {
this.isModalOpen = true;
}
@ -749,12 +666,13 @@ Open `book-list.component.ts` file in `app\books\book-list` folder and replace t
```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; //<== added this line ==>
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==>
@Component({
selector: 'app-book-list',
@ -763,17 +681,17 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; //<== added
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<Books.Book[]>;
books$: Observable<BookDto[]>;
booksType = Books.BookType;
booksType = BookType;
loading = false;
isModalOpen = false;
form: FormGroup;
form: FormGroup; // <== added this line ==>
constructor(private store: Store, private fb: FormBuilder) { } //<== added FormBuilder ==>
constructor(private store: Store, private fb: FormBuilder) {} // <== added FormBuilder ==>
ngOnInit() {
this.get();
@ -781,9 +699,10 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
}
createBook() {
@ -791,7 +710,7 @@ export class BookListComponent implements OnInit {
this.isModalOpen = true;
}
//added buildForm method
// added buildForm method
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
@ -804,6 +723,7 @@ export class BookListComponent implements OnInit {
```
* We imported `FormGroup, FormBuilder and Validators`.
* We added `form: FormGroup` variable.
* We injected `fb: FormBuilder` service to the constructor. The [FormBuilder](https://angular.io/api/forms/FormBuilder) service provides convenient methods for generating controls. It reduces the amount of boilerplate needed to build complex forms.
* We added `buildForm` method to the end of the file and executed `buildForm()` in the `createBook` method. This method creates a reactive form to be able to create a new book.
* The `group` method of `FormBuilder`, `fb` creates a `FormGroup`.
@ -880,35 +800,34 @@ export class BooksModule { }
* We imported `NgbDatepickerModule` to be able to use the date picker.
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; //<== added this line ==>
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==>
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }] //<== added this line ==>
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added this line ==>
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<Books.Book[]>;
books$: Observable<BookDto[]>;
booksType = Books.BookType;
booksType = BookType;
//added bookTypeArr array
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
bookTypeArr = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
);
loading = false;
@ -917,7 +836,7 @@ export class BookListComponent implements OnInit {
form: FormGroup;
constructor(private store: Store, private fb: FormBuilder) { }
constructor(private store: Store, private fb: FormBuilder) {}
ngOnInit() {
this.get();
@ -925,9 +844,10 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
}
createBook() {
@ -981,19 +901,26 @@ Open `book-list.component.html` in `app\books\book-list` folder and add the foll
</ng-template>
```
* This adds a save button to the bottom area of the modal:
Find the `<form [formGroup]="form">` tag and replace below content:
![Save button to the modal](./images/bookstore-new-book-form-v2.png)
```html
<form [formGroup]="form" (ngSubmit)="save()"> <!-- added the ngSubmit -->
```
* We added the `(ngSubmit)="save()"` to `<form>` element to save a new book by pressing the enter.
* We added `abp-button` to the bottom area of the modal to save a new book.
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Books } from '../../store/models';
import { GetBooks, CreateUpdateBook } from '../../store/actions'; //<== added CreateUpdateBook ==>
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks, CreateUpdateBook } from '../state/books.actions'; // <== added CreateUpdateBook ==>
import { BooksState } from '../state/books.state';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@ -1001,16 +928,17 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }]
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<Books.Book[]>;
books$: Observable<BookDto[]>;
booksType = Books.BookType;
booksType = BookType;
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
//added bookTypeArr array
bookTypeArr = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
);
loading = false;
@ -1019,7 +947,7 @@ export class BookListComponent implements OnInit {
form: FormGroup;
constructor(private store: Store, private fb: FormBuilder) { }
constructor(private store: Store, private fb: FormBuilder) {}
ngOnInit() {
this.get();
@ -1027,9 +955,10 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
}
createBook() {
@ -1062,37 +991,20 @@ export class BookListComponent implements OnInit {
```
* We imported `CreateUpdateBook`.
* We added `save` method
* We added `save` method
### Updating an existing book
The final modal UI looks like below:
#### BooksService
Open the `books.service.ts` in `app\books\shared` folder and add the `getById` and `update` methods.
```js
getById(id: string): Observable<Books.Book> {
return this.restService.request<void, Books.Book>({
method: 'GET',
url: `/api/app/book/${id}`
});
}
![Save button to the modal](./images/bookstore-new-book-form-v2.png)
update(updateBookInput: Books.CreateUpdateBookInput, id: string): Observable<Books.Book> {
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'PUT',
url: `/api/app/book/${id}`,
body: updateBookInput
});
}
```
### Updating a book
#### CreateUpdateBook action
Open the `books.actions.ts` in `app\store\actions` folder and replace the content as below:
Open the `books.actions.ts` in `books\state` folder and replace the content as below:
```js
import { Books } from '../models';
import { CreateUpdateBookDto } from '../../app/shared/models';
export class GetBooks {
static readonly type = '[Books] Get';
@ -1100,54 +1012,55 @@ export class GetBooks {
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: Books.CreateUpdateBookInput, public id?: string) { } //<== added id parameter ==>
constructor(public payload: CreateUpdateBookDto, public id?: string) { } // <== added id parameter ==>
}
```
* We added `id` parameter to the `CreateUpdateBook` action's constructor.
Open the `books.state.ts` in `app\store\states` folder and replace the `save` method as below:
Open the `books.state.ts` in `books\state` folder and replace the `save` method as below:
```js
@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
if (action.id) {
return this.booksService.update(action.payload, action.id);
} else {
return this.booksService.create(action.payload);
}
save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
if (action.id) {
return this.bookService.updateByIdAndInput(action.payload, action.id);
} else {
return this.bookService.createByInput(action.payload);
}
}
```
#### BookListComponent
Open `book-list.component.ts` in `app\books\book-list` folder and inject `BooksService` dependency by adding it to the constructor and add a variable named `selectedBook`.
Open `book-list.component.ts` in `app\books\book-list` folder and inject `BookService` dependency by adding it to the constructor and add a variable named `selectedBook`.
```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Books } from '../../store/models';
import { GetBooks, CreateUpdateBook } from '../../store/actions';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks, CreateUpdateBook } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { BooksService } from '../shared/books.service'; //<== imported BooksService ==>
import { BookService } from '../../app/shared/services'; // <== imported BookService ==>
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }]
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<Books.Book[]>;
books$: Observable<BookDto[]>;
booksType = Books.BookType;
booksType = BookType;
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
bookTypeArr = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
);
loading = false;
@ -1156,9 +1069,9 @@ export class BookListComponent implements OnInit {
form: FormGroup;
selectedBook = {} as Books.Book; //<== declared selectedBook ==>
selectedBook = {} as BookDto; // <== declared selectedBook ==>
constructor(private store: Store, private fb: FormBuilder, private booksService: BooksService) { }
constructor(private store: Store, private fb: FormBuilder, private bookService: BookService) {} //<== injected BookService ==>
ngOnInit() {
this.get();
@ -1166,39 +1079,38 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
}
//<== this method is replaced ==>
// <== this method is replaced ==>
createBook() {
this.selectedBook = {} as Books.Book; //<== added ==>
this.selectedBook = {} as BookDto; // <== added ==>
this.buildForm();
this.isModalOpen = true;
}
//<== added editBook method ==>
// <== added editBook method ==>
editBook(id: string) {
this.booksService.getById(id).subscribe(book => {
this.bookService.getById(id).subscribe((book) => {
this.selectedBook = book;
this.buildForm();
this.isModalOpen = true;
});
}
//<== this method is replaced ==>
// <== this method is replaced ==>
buildForm() {
this.form = this.fb.group({
name: [this.selectedBook.name || "", Validators.required],
name: [this.selectedBook.name || '', Validators.required],
type: [this.selectedBook.type || null, Validators.required],
publishDate: [
this.selectedBook.publishDate
? new Date(this.selectedBook.publishDate)
: null,
Validators.required
this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,
Validators.required,
],
price: [this.selectedBook.price || null, Validators.required]
price: [this.selectedBook.price || null, Validators.required],
});
}
@ -1208,7 +1120,8 @@ export class BookListComponent implements OnInit {
}
//<== added this.selectedBook.id ==>
this.store.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
this.store
.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
@ -1218,9 +1131,9 @@ export class BookListComponent implements OnInit {
}
```
* We imported `BooksService`.
* We declared a variable named `selectedBook` as `Books.Book`.
* We injected `BooksService` to the constructor. `BooksService` is being used to retrieve the book data which is being edited.
* We imported `BookService`.
* We declared a variable named `selectedBook` as `BookDto`.
* We injected `BookService` to the constructor. `BookService` is being used to retrieve the book data which is being edited.
* We added `editBook` method. This method fetches the book with the given `Id` and sets it to `selectedBook` object.
* We replaced the `buildForm` method so that it creates the form with the `selectedBook` data.
* We replaced the `createBook` method so it sets `selectedBook` to an empty object.
@ -1298,24 +1211,9 @@ Open `book-list.component.html` in `app\books\book-list` folder and find the `<n
### Deleting a book
#### BooksService
Open `books.service.ts` in `app\books\shared` folder and add the below `delete` method to delete a book.
```js
delete(id: string): Observable<void> {
return this.restService.request<void, void>({
method: 'DELETE',
url: `/api/app/book/${id}`
});
}
```
* `Delete` method gets `id` parameter and makes a `DELETE` HTTP request to the relevant endpoint.
#### DeleteBook action
Open `books.actions.ts` in `app\store\actions `folder and add an action named `DeleteBook`.
Open `books.actions.ts` in `books\state `folder and add an action named `DeleteBook`.
```js
export class DeleteBook {
@ -1324,53 +1222,58 @@ export class DeleteBook {
}
```
Open the `books.state.ts` in `app\store\states` folder and replace the content as below:
Open the `books.state.ts` in `books\state` folder and replace the content as below:
```js
import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks, CreateUpdateBook, DeleteBook } from '../actions/books.actions'; //<== added DeleteBook==>
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { GetBooks, CreateUpdateBook, DeleteBook } from './books.actions'; // <== added DeleteBook==>
import { BookService } from '../../app/shared/services';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../../app/shared/models';
@State<Books.State>({
export class BooksStateModel {
public book: PagedResultDto<BookDto>;
}
@State<BooksStateModel>({
name: 'BooksState',
defaults: { books: {} } as Books.State,
defaults: { book: {} } as BooksStateModel,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: Books.State) {
return state.books.items || [];
static getBooks(state: BooksStateModel) {
return state.book.items || [];
}
constructor(private booksService: BooksService) { }
constructor(private bookService: BookService) {}
@Action(GetBooks)
get(ctx: StateContext<Books.State>) {
return this.booksService.get().pipe(
tap(booksResponse => {
get(ctx: StateContext<BooksStateModel>) {
return this.bookService.getListByInput().pipe(
tap((booksResponse) => {
ctx.patchState({
books: booksResponse,
book: booksResponse,
});
}),
})
);
}
@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
if (action.id) {
return this.booksService.update(action.payload, action.id);
return this.bookService.updateByIdAndInput(action.payload, action.id);
} else {
return this.booksService.create(action.payload);
return this.bookService.createByInput(action.payload);
}
}
//<== added DeleteBook ==>
// <== added DeleteBook action listener ==>
@Action(DeleteBook)
delete(ctx: StateContext<Books.State>, action: DeleteBook) {
return this.booksService.delete(action.id);
delete(ctx: StateContext<BooksStateModel>, action: DeleteBook) {
return this.bookService.deleteById(action.id);
}
}
```
@ -1379,27 +1282,8 @@ export class BooksState {
- We added `DeleteBook` action listener to the end of the file.
#### Add a delete button
Open `book-list.component.html` in `app\books\book-list` folder and modify the `ngbDropdownMenu` to add the delete button as shown below:
```html
<div ngbDropdownMenu>
<!-- added Delete button -->
<button ngbDropdownItem (click)="delete(data.id, data.name)">
{%{{{ 'AbpAccount::Delete' | abpLocalization }}}%}
</button>
</div>
```
The final actions dropdown UI looks like below:
![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png)
#### Delete confirmation dialog
#### Delete confirmation popup
Open `book-list.component.ts` in`app\books\book-list` folder and inject the `ConfirmationService`.
@ -1410,26 +1294,29 @@ import { ConfirmationService } from '@abp/ng.theme.shared';
//...
constructor(
private store: Store, private fb: FormBuilder,
private booksService: BooksService,
private confirmationService: ConfirmationService // <== added this line ==>
private store: Store,
private fb: FormBuilder,
private bookService: BookService,
private confirmation: ConfirmationService // <== added this line ==>
) { }
```
* We imported `ConfirmationService`.
* We injected `ConfirmationService` to the constructor.
See the [Confirmation Popup documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Confirmation-Service)
In the `book-list.component.ts` add a delete method :
```js
import { GetBooks, CreateUpdateBook, DeleteBook } from '../../store/actions'; //<== added DeleteBook ==>
import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/books.actions' ;// <== imported DeleteBook ==>
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== added Confirmation ==>
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation ==>
//...
delete(id: string, name: string) {
this.confirmationService
delete(id: string) {
this.confirmation
.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure')
.subscribe(status => {
if (status === Confirmation.Status.confirm) {
@ -1439,10 +1326,30 @@ delete(id: string, name: string) {
}
```
The `delete` method shows a confirmation popup and subscribes for the user response. `DeleteBook` action dispatched only if user clicks to the `Yes` button. The confirmation popup looks like below:
![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png)
#### Add a delete button
Open `book-list.component.html` in `app\books\book-list` folder and modify the `ngbDropdownMenu` to add the delete button as shown below:
```html
<div ngbDropdownMenu>
<!-- added Delete button -->
<button ngbDropdownItem (click)="delete(data.id)">
{%{{{ 'AbpAccount::Delete' | abpLocalization }}}%}
</button>
</div>
```
The final actions dropdown UI looks like below:
![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png)
{{end}}
### Next Part

BIN
docs/en/Tutorials/images/generate-proxy-command.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/en/Tutorials/images/generated-proxies.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

1
templates/app/angular/package.json

@ -47,7 +47,6 @@
"karma-jasmine": "~3.0.1",
"karma-jasmine-html-reporter": "^1.4.2",
"ng-packagr": "^9.0.0",
"ngxs-schematic": "^1.1.9",
"protractor": "~5.4.3",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",

Loading…
Cancel
Save