Skip to content

Instantly share code, notes, and snippets.

@vuquangtam
Created September 12, 2019 03:17
Show Gist options
  • Save vuquangtam/c46a53065585b8a1a33a8dcbf0351759 to your computer and use it in GitHub Desktop.
Save vuquangtam/c46a53065585b8a1a33a8dcbf0351759 to your computer and use it in GitHub Desktop.
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