Created
September 12, 2019 03:17
-
-
Save vuquangtam/c46a53065585b8a1a33a8dcbf0351759 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
REDUX - NGRX | |
_____ | |
* Introduction | |
- Redux is predictable __application__ state management library created by Dan Abramov. | |
- Inspired from Flux architecture of Facebook. | |
- Ngrx is set of library support Redux for Angular. | |
** Principles | |
- Single source of truth (Store) | |
- State is read-only | |
- Pure function reducers | |
* The Problem | |
** Component start to receive props that feel extraneous | |
#+BEGIN_SRC js | |
// componentA component | |
<componentB [options]="..." (onChange)="..."></componentB> | |
// parent component | |
<app> | |
<componentA [options]="..." (onChange)="..."></componentA> | |
</app> | |
#+END_SRC | |
** Multiple View-Model for the same business data (Model) | |
- Every View-Model sync and consistent when Model change | |
** Multiple independent actors (server-push and user interaction) that may mutate data | |
** Avoid AJAX spinner. | |
* Service Store Architecture | |
- Business logic provide in service as observable. | |
- Smart component inject service and subscribe for handle business logic | |
- Smart component doesn't has any state variables inside | |
- Render data by using async pipe. | |
** Example | |
#+BEGIN_SRC js | |
// Service | |
@Injectable() | |
export class TodoStore { | |
private _todos: BehaviorSubject<List<Todo>> = new BehaviorSubject(List([])); | |
public readonly todos: Observable<List<Todo>> = this._todos.asObservable(); | |
constructor(private todoBackendService: TodoBackendService) { | |
this.loadInitialData(); | |
} | |
addTodo(newTodo:Todo):Observable { | |
let obs = this.todoBackendService.saveTodo(newTodo); | |
obs.subscribe( | |
res => { | |
this._todos.next(this._todos.getValue().push(newTodo)); | |
}); | |
return obs; | |
} | |
... | |
} | |
#+END_SRC | |
#+BEGIN_SRC js | |
export class AppComponent { | |
constructor(public todoStore: TodoStore) { | |
} | |
onAddTodo(todo) { | |
this.todoStore.addTodo(todo) | |
.subscribe( | |
res => {}, | |
err => {} | |
); | |
} | |
} | |
#+END_SRC | |
#+BEGIN_SRC js | |
<ul id="todo-list"> | |
<li *ngFor="let todo of todoStore.todos | async" > | |
... | |
</li> | |
</ul> | |
#+END_SRC | |
* Ngrx | |
** Architecture | |
- https://s3.amazonaws.com/media-p.slid.es/uploads/77451/images/4514092/Screen_Shot_2018-01-16_at_10.36.48_AM.png | |
*** Action | |
- Plain js object that consisting of TYPE with optional payload to mutate Store. | |
#+BEGIN_SRC js | |
{ | |
type: 'TODO_ADD', | |
payload: {...} | |
} | |
#+END_SRC | |
- To call action we use dispatch method on Store. | |
**** Action Creator | |
- A class (functional) factory to generate action | |
#+BEGIN_SRC js | |
// Action with payload | |
export class AddTodoAction implements Action { | |
readonly type = types.TODO_ADD'; | |
constructor(public payload: { data: Todo }) {} | |
} | |
#+END_SRC | |
*** Reducer | |
- Reducers are pure functions that takes the data passed by action and a previous state, and return a new state depending on the action type. They don’t modify the existing state, instead they return a new state. | |
#+BEGIN_SRC js | |
export function CustomerReducer(state: initialState, action: customerActions.Actions) { | |
switch(action.type) { | |
case types.LOAD_CUSTOMERS_SUCCESS: { | |
return { ...state, customers: action.payload }; | |
} | |
default: | |
return state; | |
} | |
} | |
#+END_SRC | |
*** Effect | |
- Effects handle side-effects (HTTP, setTimeout, ...) | |
- Effects listens for dispatched actions in an observable stream, performs side effects, and returns new actions either immediately or asynchronously | |
#+BEGIN_SRC js | |
@Effect() loadCustomers$: Observable<Action> = this.actions$.pipe( | |
ofType<customerActions.loadCustomersAction>(types.LOAD_CUSTOMERS), | |
mergeMap(() => this.customersService.getCustomers().pipe( | |
map(customers => (new customerActions.loadCustomersSuccessAction(customers))) | |
)) | |
) | |
#+END_SRC | |
*** Selector | |
- Selectors are pure functions that take slices of state as arguments and return some state data that we can pass to our components. | |
#+BEGIN_SRC js | |
this.state = { | |
products: { | |
pizzas: { | |
entities: { | |
1: { name: 'Pizza 1', id: 1 }, | |
2: { name: 'Pizza 2', id: 2 }, | |
}, | |
}, | |
}, | |
}; | |
#+END_SRC | |
#+BEGIN_SRC js | |
state | |
-> products | |
-> pizzas | |
-> entities | |
#+END_SRC | |
#+BEGIN_SRC js | |
const getProducts = state => state.products; | |
const getPizzas = createSelector( | |
getProducts, | |
state => state.pizzas | |
); | |
const getEntities = createSelector( | |
getPizzas, | |
state => state.entities | |
); | |
#+END_SRC | |
*** Entity | |
- EntityState | |
#+BEGIN_SRC js | |
interface EntityState<V> { | |
ids: string[]; | |
entities: { [id: string]: V }; | |
} | |
#+END_SRC | |
- Reduce boilerate when working with collections | |
+ No longer have to explicitly declare all of the properties for the state interface | |
+ The implementation for adding, deleting, or updating entities in the state are all handled by the adapter. | |
+ The adapter generates a set of commonly used selectors for you. | |
#+BEGIN_SRC js | |
// action | |
import { Action } from '@ngrx/store'; | |
export enum BookActionTypes { | |
ADD_ONE = '[Books] Add One', | |
UPDATE_ONE = '[Books] Update One', | |
DELETE_ONE = '[Books] Delete One', | |
GET_ALL = '[Books] Get All' | |
} | |
export class AddOne implements Action { | |
readonly type = BookActionTypes.ADD_ONE; | |
constructor(public book: BookModel) { } | |
} | |
export class UpdateOne implements Action { | |
readonly type = BookActionTypes.UPDATE_ONE; | |
constructor( | |
public id: string, | |
public changes: Partial<BookModel>, | |
) { } | |
} | |
export class DeleteOne implements Action { | |
readonly type = BookActionTypes.DELETE_ONE; | |
constructor(public id: string) { } | |
} | |
export class GetAll implements Action { | |
readonly type = BookActionTypes.GET_ALL; | |
constructor(public books: BookModel[]) { } | |
} | |
export type BookActions | |
= GetOne | |
| UpdateOne | |
| DeleteOne | |
| GetAll; | |
// reducer | |
const initialState: BookState = bookAdapter.getInitialState(); | |
export function bookReducer( | |
state: BookState = initialState, | |
action: BookActions, | |
): BookState { | |
switch (action.type) { | |
case BookActionTypes.ADD_ONE: | |
return bookAdapter.addOne(action.book, state); | |
case BookActionTypes.UPDATE_ONE: | |
return bookAdapter.updateOne({ | |
id: action.id, | |
changes: action.changes, | |
}, state); | |
case BookActionTypes.DELETE_ONE: | |
return bookAdapter.deleteOne(action.id, state); | |
case BookActionTypes.GET_ALL: | |
return bookAdapter.addAll(action.books, state); | |
default: | |
return state; | |
} | |
} | |
export const { | |
selectIds, | |
selectEntities, | |
selectAll, | |
selectTotal, | |
} = bookAdapter.getSelectors(); | |
#+END_SRC | |
- Resource: https://medium.com/ngrx/introducing-ngrx-entity-598176456e15 | |
** Dataflow | |
- https://www.slideshare.net/koumatsumoto/ngrx-84057420 | |
** Pattern | |
- https://blog.nrwl.io/ngrx-patterns-and-techniques-f46126e2b1e5 | |
** Tooling | |
- Redux devtools |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment