So You Wanna Do Redux?

http://briebug.github.io/presentations/ng-meetup/2018/01/ngrx-upgrade
Jesse Sanders
@JesseS_BrieBug
https://github.com/jessesanders
jesse.sanders@briebug.com

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)
);
          
        

References