Latest updates in NgRx v8

  • img
    Abin Punnoose
  • August 6,2019

NgRx, the popular State Management Library for Angular released its latest version in the first week week of June. There are a lot of interesting and exciting changes in this version.

I will attempt to list down the updates which I think are interesting or major. This post will include the changes in NgRx v8 compared to NgRx v7.

@ngrx/data

The hugely popular angular-ngrx-data package created by John Papa and Ward Bell has been made part of the official @ngrx package. The original package became popular among developers which helped in reducing ngrx boiler plate code.

@ngrx/store

There have been a host of changes in the @ngrx/store package aimed at reducing the boilerplate.

createAction

In NgRx 8, createAction has been introduced which is a factory method to declare actions. This leads to a lot of advantages including we don't have to create actionTypes or ActionUnions anymore. This makes creating actions less verbose and easier.

The createAction method accepts two parameters, the first being the action type and the second being the props parameter which replaces the payload feature in old version of actions.

So the following are some actions in NgRx 7 listed below.

import { Action } from '@ngrx/store';
import { Property } from '../models';

export enum ActionTypes {
  LOAD_PROPERTIES = '[Properties Page] Load Properties',
  LOAD_PROPERTIES_FAILURE = '[Properties API] Load Properties Failure',
  LOAD_PROPERTIES_SUCCESS = '[Properties API] Load Properties Success'
}

export class LoadPropertiesAction implements Action {
  readonly type = ActionTypes.LOAD_PROPERTIES;
}

export class LoadPropertiesFailureAction implements Action {
  readonly type = ActionTypes.LOAD_PROPERTIES_FAILURE;
  constructor(public payload: { error: string }) {}
}

export class LoadPropertiesSuccessAction implements Action {
  readonly type = ActionTypes.LOAD_PROPERTIES_SUCCESS;
  constructor(public payload: { properties: Property[] }) {}
}

export type ActionsUnion = LoadPropertiesAction | LoadPropertiesFailureAction | LoadPropertiesSuccessAction;

When the above actions are updated to NgRx 8 using createAction, the code becomes as follows.

import { Action, props } from '@ngrx/store';
import { Property } from '../models';

export const loadProperties = createAction('[Properties Page] Load Properties');
export const loadPropertiesFailure = createAction('[Properties API] Load Properties Failure', props<{error: string}>());
export const loadPropertiesSuccess = createAction('[Properties API] Load Properties Success', props<{properties: Property[]}>());

There has been a massive reduction of lines of code and you don't have to worry about forgetting to add your new action to the ActionUnions and wonder why the new Action is not working(maybe this happens with me only).

There has been a minor change in the way we dispatch actions from the Component. We don't have to use the new keyword anymore because our actions are not Classes anymore but a factory method which returns a function called ActionCreator.

this.store.dispatch(loadProperties());

createReducer

The createReducer function has been introduced in NgRx 8 where we don't have to use switch case statements anymore, instead use on method in place.

The on method has 2 parameters, the first parameter being the Action and the second being a handler which takes the old state as input and returns the new state as output.

Continuing with the same example, the reducer corresponding to the above created actions in NgRx 7 is shown below.

import { ActionsUnion, ActionTypes } from './actions';
import { Property } from '../models';

export interface State {
  properties: Property[];
  isLoading: boolean;
  error: string | null;
}

export const initialState: State = {
  properties: [];
  isLoading: false;
  error: null;
};

export function featureReducer(state = initialState, action: ActionsUnion): State {
  switch (action.type) {
    case ActionTypes.LOAD_PROPERTIES: {
      return {
        ...state,
        isLoading: true,
        error: null
      };
    }
    case ActionTypes.LOAD_PROPERTIES_SUCCESS: {
      return {
        ...state,
        isLoading: false,
        error: null,
        properties: action.payload.properties
      };
    }
    case ActionTypes.LOAD_PROPERTIES_FAILURE: {
      return {
        ...state,
        isLoading: false,
        error: action.payload.error
      };
    }
    default: {
      return state;
    }
  }
}

The above code when re-factored into the latest NgRx 8 using createReducer results in a reduction in lines.

import { createReducer, on } from '@ngrx/store`;
import * as propertyActions from './actions';
import { Property } from '../models';

export interface State {
  properties: Property[];
  isLoading: boolean;
  error: string | null;
}

export const initialState: State = {
  properties: [];
  isLoading: false;
  error: null;
};

const featureReducer = createReducer(
  initialState,
  on(propertyActions.loadProperties, state => ({ ...state, isLoading: true, error: null })),
  on(propertyActions.loadPropertiesSuccess, (state, { properties }) => ({ ...state, isLoading: false, error: null, properties })),
  on(propertyActions.loadPropertiesFailure, (state, { error }) => ({ ...state, isLoading: false, error })),
);

export function reducer(state: State | undefined, action: Action) {
  return featureReducer(state, action);
}

We don't have to worry about the default state any more which is cool.

@ngrx/effects

createEffect

If they updated createAction and createReducer, they are not going to forget about Effects, are they?

So, they came up with createEffect function. You don't have to use the @Effect() decorator anymore.

Repeating the example from above for the Effect part also, the following code snippet is how Effects are defined in NgRx 7.

@Effect()
  loadPropertiesEffect$: Observable<Action> = this.actions$.pipe(
    ofType<featureActions.LoadPropertiesAction>(
      featureActions.ActionTypes.LOAD_PROPERTIES
    ),
    concatMap(action =>
      this.propertyService
        .getProperties()
        .pipe(
          map(
            properties =>
              new featureActions.LoadPropertiesSuccessAction({
                properties
              })
          ),
          catchError(error =>
            observableOf(new featureActions.LoadPropertiesFailureAction({ error: error.message }))
          )
        )
    )
  );

The same code when refactored into NgRx 8 using createEffect is as follows:

loadPropertiesEffect$ = createEffect(() => this.actions$.pipe(
        ofType(featureActions.loadProperties),
        concatMap(action =>
        this.propertyService
            .getProperties()
            .pipe(
                map(properties => featureActions.loadPropertiesSuccess({fruits})),
                catchError(error =>
                    observableOf(featureActions.loadPropertiesFailure({ error: error.message }))
                )
            )
        )
    )
  );

Minor adjustments in the syntax which will need time getting used to. Maybe, not for you guys, but definitely for me.

Conclusion

These are the changes which are noteworthy and worth it if you decide to refactor your code. Do note, but the old syntax of NgRx 7 is still supported and these new functions are optional. That said, it's always good advice if you can refactor to reflect the new changes, because the old syntax will get deprecated at some point in the future. But, if you are lazy like me, don't bother until they do!

For a whole list of updates, checkout the official blog of NgRx. For an upgrade guide, checkout this blog on Ultimate Courses by Wes Grimes. If you are an NgRx noobie, check the official website for a starting point.

To all the kind souls, who reached this point of the blog, I am really grateful!

Subscribe to newsletter
Need more tech news? Tune in to our weekly newsletter to get the latest updates