@ -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 books Service: Books Service) { }
constructor(private bookService: BookService) {}
@Action (GetBooks)
get(ctx: StateContext< Books. State > ) {
return this.books Service.get().pipe(
tap(books Response => {
get(ctx: StateContext< BooksStateModel > ) {
return this.bookService.getListByInput().pipe(
tap((bookResponse) => {
ctx.patchState({
books : books Response,
book: bookResponse,
});
}),
})
);
}
//added CreateUpdateBook action listener
// added CreateUpdateBook action listener
@Action (CreateUpdateBook)
save(ctx: StateContext< Books. State > , action: CreateUpdateBook) {
return this.books Service.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 `Books Service` .
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.Book Type;
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.Book Type;
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.Book Type;
booksType = BookType;
//added bookTypeArr array
bookTypeArr = Object.keys(Books.Book Type).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 :

```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.Book Type;
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}`
});
}

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.books Service.update(action.payload, action.id);
} else {
return this.books Service.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 `Books Service` 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 { Books Service } from '../shared/books. service'; //< == imported Books Service ==>
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.Book Type;
booksType = BookType;
bookTypeArr = Object.keys(Books.Book Type).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.books Service.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 `Books Service` .
* We declared a variable named `selectedBook` as `Books.Book ` .
* We injected `Books Service` to the constructor. `Books Service` 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 books Service: Books Service) { }
constructor(private bookService: BookService) {}
@Action (GetBooks)
get(ctx: StateContext< Books. State > ) {
return this.books Service.get().pipe(
tap(booksResponse => {
get(ctx: StateContext< BooksStateModel > ) {
return this.bookService.getListByInpu t().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.books Service.update(action.payload, action.id);
return this.bookService.updateByIdAndInput (action.payload, action.id);
} else {
return this.books Service.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.books Service.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:

#### 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'; //< == add ed DeleteBook ==>
import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/books.actions' ;// < == import ed DeleteBook ==>
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //< == add ed Confirmation ==>
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //< == import ed 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:

#### 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:

{{end}}
### Next Part