diff --git a/src/app/books/actions/books-api.actions.ts b/src/app/books/actions/books-api.actions.ts index 5326615..3dfad86 100644 --- a/src/app/books/actions/books-api.actions.ts +++ b/src/app/books/actions/books-api.actions.ts @@ -1,2 +1,29 @@ import { Book } from "src/app/shared/models/book.model"; -import { Action } from "@ngrx/store"; +import { createAction, props } from "@ngrx/store"; + +export const booksLoaded = createAction( + "[Books API] Books Loaded Success", + props<{ books: Book[] }>() +); + +export const bookCreated = createAction( + "[Books API] Book Created", + props<{ book: Book }>() +); + +export const bookUpdated = createAction( + "[Books API] Book Updated", + props<{ book: Book }>() +); + +export const bookDeleted = createAction( + "[Books API] Book Deleted", + props<{ book: Book }>() +); + +export type BooksApiActions = ReturnType< + | typeof booksLoaded + | typeof bookCreated + | typeof bookUpdated + | typeof bookDeleted +>; diff --git a/src/app/books/actions/books-page.actions.ts b/src/app/books/actions/books-page.actions.ts index 289e7f4..0de5068 100644 --- a/src/app/books/actions/books-page.actions.ts +++ b/src/app/books/actions/books-page.actions.ts @@ -1,52 +1,37 @@ -import { Book } from "src/app/shared/models/book.model"; -import { Action } from "@ngrx/store"; - -export enum BooksActionTypes { - Enter = '[Books Page] Enter', - SelectBook = '[Books Page] Select Book', - ClearSelectedBook = '[Books Page] Clear Selected Book', - CreateBook = '[Books Page] Create Book', - UpdateBook = '[Books Page] Update Book', - DeleteBook = '[Books Page] Delete Book' -} - -export class Enter implements Action { - readonly type = BooksActionTypes.Enter; -} - -export class SelectBook implements Action { - readonly type = BooksActionTypes.SelectBook; - - constructor(public bookId: string) {} -} - -export class ClearSelectedBook implements Action { - readonly type = BooksActionTypes.ClearSelectedBook; -} - -export class CreateBook implements Action { - readonly type = BooksActionTypes.CreateBook; - - constructor(public book: Book) {} -} - -export class UpdateBook implements Action { - readonly type = BooksActionTypes.UpdateBook; - - constructor(public book: Book) {} -} - -export class DeleteBook implements Action { - readonly type = BooksActionTypes.DeleteBook; - - constructor(public book: Book) {} -} - -export type BooksActions = - | Enter - | SelectBook - | ClearSelectedBook - | CreateBook - | UpdateBook - | DeleteBook -; +import { createAction, props } from "@ngrx/store"; +import { BookRequiredProps, Book } from "src/app/shared/models/book.model"; + +export const enter = createAction("[Books Page] Enter"); + +export const selectBook = createAction( + "[Books Page] Select Book", + props<{ bookId: string }>() +); + +export const clearSelectedBook = createAction( + "[Books Page] Clear Selected Book" +); + +export const createBook = createAction( + "[Books Page] Create Book", + props<{ book: BookRequiredProps }>() +); + +export const updateBook = createAction( + "[Books Page] Update Book", + props<{ book: Book; changes: BookRequiredProps }>() +); + +export const deleteBook = createAction( + "[Books Page] Delete Book", + props<{ book: Book }>() +); + +export type BooksActions = ReturnType< + | typeof enter + | typeof selectBook + | typeof clearSelectedBook + | typeof createBook + | typeof updateBook + | typeof deleteBook +>; diff --git a/src/app/books/books-api.effects.ts b/src/app/books/books-api.effects.ts new file mode 100644 index 0000000..cbc2496 --- /dev/null +++ b/src/app/books/books-api.effects.ts @@ -0,0 +1,63 @@ +import { Injectable } from '@angular/core'; +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { BooksPageActions, BooksApiActions } from './actions'; +import { BooksService } from '../shared/services/book.service'; +import { mergeMap, map, catchError } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; + +@Injectable() +export class BooksApiEffects { + + @Effect() + loadBooks$ = this.actions$.pipe( + ofType(BooksPageActions.enter.type), + mergeMap(() => + this.booksService.all() + .pipe( + map(books => BooksApiActions.booksLoaded({books})), + catchError(() => EMPTY) + ) + ) + ); + + @Effect() + createBook$ = this.actions$.pipe( + ofType(BooksPageActions.createBook.type), + mergeMap(action => + this.booksService.create(action.book) + .pipe( + map(book => BooksApiActions.bookCreated({book})), + catchError(() => EMPTY) + ) + ) + ); + + @Effect() + updateBook$ = this.actions$.pipe( + ofType(BooksPageActions.updateBook.type), + mergeMap(action => + this.booksService.update(action.book.id, action.book) + .pipe( + map(book => BooksApiActions.bookUpdated({book})), + catchError(() => EMPTY) + ) + ) + ); + + @Effect() + deleteBook$ = this.actions$.pipe( + ofType(BooksPageActions.deleteBook.type), + mergeMap(action => + this.booksService.delete(action.book.id) + .pipe( + map(() => BooksApiActions.bookDeleted({ book: action.book})), + catchError(() => EMPTY) + ) + ) + ); + + constructor( + private booksService: BooksService, + private actions$: Actions<BooksPageActions.BooksActions | BooksApiActions.BooksApiActions> + ) {} +} \ No newline at end of file diff --git a/src/app/books/books.module.ts b/src/app/books/books.module.ts index 2239fb8..0b4e223 100644 --- a/src/app/books/books.module.ts +++ b/src/app/books/books.module.ts @@ -10,6 +10,9 @@ import { BookDetailComponent } from './components/book-detail/book-detail.compon import { BooksListComponent } from './components/books-list/books-list.component'; import { BooksTotalComponent } from './components/books-total/books-total.component'; +import { EffectsModule } from '@ngrx/effects'; +import { BooksApiEffects } from './books-api.effects'; + @NgModule({ imports: [ CommonModule, @@ -18,6 +21,7 @@ import { BooksTotalComponent } from './components/books-total/books-total.compon RouterModule.forChild([ { path: 'books', component: BooksPageComponent } ]), + EffectsModule.forFeature([BooksApiEffects]) ], declarations: [ BooksPageComponent, diff --git a/src/app/books/components/books-page/books-page.component.ts b/src/app/books/components/books-page/books-page.component.ts index 4ab8c95..1b3fbfe 100755 --- a/src/app/books/components/books-page/books-page.component.ts +++ b/src/app/books/components/books-page/books-page.component.ts @@ -5,7 +5,6 @@ import { Book } from 'src/app/shared/models/book.model'; import { Observable } from 'rxjs'; import { Store, select } from '@ngrx/store'; import * as fromRoot from 'src/app/shared/state'; -import { map, tap } from 'rxjs/operators'; import { BooksPageActions } from '../../actions'; @Component({ @@ -32,11 +31,11 @@ export class BooksPageComponent implements OnInit { } getBooks() { - this.store.dispatch(new BooksPageActions.Enter()); + this.store.dispatch(BooksPageActions.enter()); } onSelect(book: Book) { - this.store.dispatch(new BooksPageActions.SelectBook(book.id)); + this.store.dispatch(BooksPageActions.selectBook({bookId: book.id})); } onCancel() { @@ -44,7 +43,7 @@ export class BooksPageComponent implements OnInit { } removeSelectedBook() { - this.store.dispatch(new BooksPageActions.ClearSelectedBook()); + this.store.dispatch(BooksPageActions.clearSelectedBook()); } onSave(book: Book) { @@ -56,14 +55,14 @@ export class BooksPageComponent implements OnInit { } saveBook(book: Book) { - this.store.dispatch(new BooksPageActions.CreateBook(book)); + this.store.dispatch(BooksPageActions.createBook({book})); } updateBook(book: Book) { - this.store.dispatch(new BooksPageActions.UpdateBook(book)); + this.store.dispatch(BooksPageActions.updateBook({book, changes: book})); } onDelete(book: Book) { - this.store.dispatch(new BooksPageActions.DeleteBook(book)); + this.store.dispatch(BooksPageActions.deleteBook({book})); } } diff --git a/src/app/shared/state/books.reducer.ts b/src/app/shared/state/books.reducer.ts index 4856201..78e773a 100644 --- a/src/app/shared/state/books.reducer.ts +++ b/src/app/shared/state/books.reducer.ts @@ -1,6 +1,6 @@ import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity'; import { Book } from 'src/app/shared/models/book.model'; -import { BooksPageActions } from 'src/app/books/actions'; +import { BooksPageActions, BooksApiActions } from 'src/app/books/actions'; export const initialBooks: Book[] = [ @@ -34,28 +34,30 @@ export const initialState = adapter.getInitialState({ activeBookId: null }); -export function reducer(state = initialState, action: BooksPageActions.BooksActions): State { +export function reducer(state = initialState, action: BooksPageActions.BooksActions | BooksApiActions.BooksApiActions): State { switch(action.type) { - case BooksPageActions.BooksActionTypes.Enter: - return adapter.addAll(initialBooks, state); + case BooksApiActions.booksLoaded.type: + return adapter.addAll(action.books, state); - case BooksPageActions.BooksActionTypes.SelectBook: + case BooksPageActions.selectBook.type: return { ...state, activeBookId: action.bookId }; - case BooksPageActions.BooksActionTypes.ClearSelectedBook: + + case BooksPageActions.clearSelectedBook.type: return { ...state, activeBookId: null }; - case BooksPageActions.BooksActionTypes.CreateBook: - return adapter.addOne(action.book, state); - case BooksPageActions.BooksActionTypes.UpdateBook: + case BooksApiActions.bookCreated.type: + return adapter.addOne(action.book, {...state, activeBookId: action.book.id}); + + case BooksApiActions.bookUpdated.type: return adapter.updateOne({id: action.book.id, changes: action.book}, {...state, activeBookId: action.book.id}); - case BooksPageActions.BooksActionTypes.DeleteBook: + case BooksApiActions.bookDeleted.type: return adapter.removeOne(action.book.id, {...state, activeBookId: null}); default: