Intro to RxJs?
2/7/2018
Brian Love
Agenda
When/Why to implement redux
Planning
Container Components
Presentational Components
Identifying Entities, Create Models
Model Interface Example
export interface IArticle {
_id: string;
tags: string[];
title: string;
url: string;
version: string;
author_name: string;
status: string;
published_date: Date;
submitted_by_id: number;
submitted_by_name: string;
submitted_by_email: string;
}
Identifying Actions
Creating Actions
Action Enum Example
export enum ArticleActionTypes {
LoadRecent = '[Article] Load Recent',
Search = '[Article] Search',
LoadSuccess = '[Article] Load Success',
LoadFail = '[Article] Load Fail',
Select = '[Article] Select'
}
Creating Action Classes
Action Class Examples
export class LoadRecentArticles implements Action {
readonly type = ArticleActionTypes.LoadRecent;
}
export class LoadSuccess implements Action {
readonly type = ArticleActionTypes.LoadSuccess;
constructor(public payload: IArticle[]) { }
}
Exporting Action Class Types
Exporting Action Class Types Examples
export type ArticleActions
= LoadRecentArticles
| SearchArticles
| LoadSuccess
| LoadFail;
Creating Reducers
NGRX Entity
Reducer EntityState Example
export interface AppState {
articles: IArticle[];
articleFilter: IArticleFilter;
}
export interface ArticlesState extends EntityState<IArticle> {
selectedArticleId: number | null;
loading: boolean;
error: any;
}
Reducer Adapter Example
export const articleAdapter: EntityAdapter<IArticle> =
createEntityAdapter<IArticle>({
selectId: (article: IArticle) => article._id,
sortComparer: false
});
export const initialState: ArticlesState =
articleAdapter.getInitialState({
selectedArticleId: null,
loading: false,
error: null
});
Reducer Example
export function articleReducer(
state = initialState,
action: ArticleActions
): ArticlesState {
switch (action.type) {
case ArticleActionTypes.Load:
case ArticleActionTypes.LoadRecent:
case ArticleActionTypes.Search:
return {
...state,
...articleAdapter.removeAll(state),
loading: true,
error: null
};
case ArticleActionTypes.LoadSuccess:
return {
...articleAdapter.addMany(action.payload, initialState),
loading: false
};
case ArticleActionTypes.LoadFail:
return {
...state,
loading: false,
error: { error: 'Error while loading articles' }
};
case ArticleActionTypes.Select: {
return {
...state,
selectedArticleId: action.payload,
};
}
default:
return state;
}
}
Effects
Original Code Example
getRecentArticles() {
this.loading = true;
this.articleSvc.getRecentArticles()
.subscribe(articles => {
this.articles = articles;
this.ready();
});
// No error handling... very bad.
}
Effect Example
@Effect()
loadRecentArticles: Observable<Action> =
this.actions.ofType(ArticleActionTypes.LoadRecent)
.switchMap(() =>
this.articleSvc.getRecentArticles()
.pipe(
map((articles: IArticle[]) =>
new articleActions.LoadArticlesSuccess(articles)),
catchError(err =>
of(new articleActions.LoadArticlesFail({ error: err.message })))
)
);
Selectors
Default Adapter Selectors
// TypeScript Destructuring
export const {
selectIds: selectArticleIds,
selectEntities: selectArticleEntities,
selectAll: selectAllArticles,
selectTotal: selectArticleTotal
} = articleAdapter.getSelectors(selectArticleState);
Custom Selectors
export const selectAllArticleTags = createSelector(
selectAllArticles,
(articles: IArticle[]) =>
articles.map(article => article.tags)
);