Adrian Faciu
Principal software engineer. Focused on front-end. Learning something new each day. Building things at WeVideo.
developer
Redux ? This again ...
Redux is a predictable state container for JavaScript apps
+
+
export const increment = { type: 'INCREMENT' };
export const increment = {
type: 'INCREMENT',
payload: 10,
};
export const increment = createAction('INCREMENT');
export const decrement = createAction('DECREMENT');
increment(); // { type: 'INCREMENT' }
decrement(); // { type: 'DECREMENT' }
incement(10); // { type: 'INCREMENT', payload: 10 }
decrement([1, 42]); // { type: 'DECREMENT', payload: [1, 42] }
import { Action } from '@ngrx/store';
export const INCREMENT = 'INCREMENT';
export class Increment implements Action {
readonly type = INCREMENT;
public payload: number;
constructor(payload: number) {
this.payload = payload;
}
}
import { Action } from '@ngrx/store';
export const INCREMENT = 'INCREMENT';
export class Increment implements Action {
readonly type = INCREMENT;
constructor(public payload: number) { }
}
import { Action } from '@ngrx/store';
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export class Increment implements Action {
readonly type = INCREMENT;
constructor(public payload: number) { }
}
export class Decrement implements Action {
readonly type = DECREMENT;
constructor(public payload: number) { }
}
export type Actions = Increment | Decrement;
import * as appActions from './actions/app.actions';
// Dispatch
store.dispatch(new appActions.Increment(5));
// Reducer
export function Reducer(
state: AppState,
action: appActions.Actions,
) {
switch (action.type) {
case appActions.INCREMENT:
return { counter: state.counter + action.payload };
default:
return state;
}
}
import { Action } from '@ngrx/store';
export const LOG_ERROR = 'LOG_ERROR';
export class LogError implements Action {
readonly type = LOG_ERROR;
constructor(public payload: { message: string }) { }
}
export const LOG_ERROR = '[Logger] Log Error';
export const LOG_WARNING = '[Logger] Log Warning';
export const FETCH_LOGS = '[Logger] Fetch';
function toPayload<T>(action: { payload: T }) {
return action.payload;
}
const toPayload = <T>(action: { payload: T }) => action.payload;
@Effect()
public errors$: Observable<Action> = this.actions$
.pipe(
ofType(LOG_ERROR),
map(toPayload),
tap(payload => console.log(payload)),
...
);
@Effect({ dispatch: false })
public userLogout$: Observable<Action> = this.actions$
.pipe(
ofType(userActions.Logout),
tap(action => console.log('Logging out', action)),
);
export interface AppState {
user: User;
logs: Logs[],
....
}
export const getUser = (state: AppState) => state.user;
const user = store.select(getUser);
import { createSelector } from '@ngrx/store';
export const getUserLanguage = createSelector(
getUser,
(user) => user.language,
);
export const getUserLogs = createSelector(
getUser,
getLogs,
(user, logs) => logs.filter(log => log.userId === user.id),
);
function reducer(state = initialState, action: AuthActionsUnion) {
switch (action.type) {
case AuthActionTypes.LoginSuccess: {
return {
...state,
loggedIn: true,
user: action.payload.user,
};
}
case AuthActionTypes.Logout: {
return initialState;
}
default: {
return state;
}
}
}
export function reducer(
state = initialState,
action: BookActionsUnion | CollectionActionsUnion
): State {
switch (action.type) {
case BookActionTypes.SearchComplete:
case CollectionActionTypes.LoadSuccess: {
return {
...state,
books: action.payload,
};
}
case BookActionTypes.Load: {
return {
...state,
isLoading: true,
};
}
case BookActionTypes.Select: {
return {
...state,
selectedBookId: action.payload,
};
}
default: {
return state;
}
}
}
don't duplicate
avoid (deeply) nested objects
store as objects, not as arrays
use helpers libraries if needed
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
}
]
}
{
result: "123",
entities: {
"articles": {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324" ]
}
},
"users": {
"1": { "id": "1", "name": "Paul" },
"2": { "id": "2", "name": "Nicole" }
},
"comments": {
"324": { id: "324", "commenter": "2" }
}
}
}
const first$ = stream$.pipe(map(_ => MY_CONSTANT));
const second$ = stream$.pipe(switchMap(() => of(MY_CONSTANT)));
const first$ = stream$.pipe(mapTo(MY_CONSTANT));
const second$ = stream$.pipe(switchMapTo(of(MY_CONSTANT)));
export class ErrorOccurred implements Action {
readonly type = ERROR_OCCURRED;
constructor(
public payload: {
action?: Action;
error?: ErrorData;
},
) {}
}
@Effect()
loadDocuments$ = this.actions$.pipe(
ofType<documentActions.Fetch>(documentActions.FETCH)
switchMapTo(
this.documentsService
.getDocuments().pipe(
map(docs => new documentActions.FetchSuccess(docs)),
catchError(error => of(
new ErrorOccurred({
action: { type: documentActions.FETCH },
error,
})
)),
)
)
)
By Adrian Faciu
Some best practices when using NgRx
Principal software engineer. Focused on front-end. Learning something new each day. Building things at WeVideo.