Learn Angular The Right Way Angular Icon

Angular is complex, unless you have all the answers...

How to upgrade your Angular and NgRx Apps to v8

Do you have an awesome application written with Angular v7 using NgRx v7, but have been feeling left out will all the mentions online and at conferences about Angular v8 and NgRx v8? Well, you are in luck! Today we will explore together, how to upgrade our applications to use Angular v8 using the Angular CLI tooling. We will also explore upgrading to NgRx v8. This will allow us to take advantage of the new features provided in NgRx v8. Included with NgRx v8 is a shiny set of creators, or type-safe factory functions, for actions, effects, and reducers.

Upgrading Dependencies

Upgrading Angular

The Angular team has provided a great website that walks through the process of upgrading in-depth. This website can be found at Angular Update Tool. We will touch on some of the information today.

The first step in the process is to upgrade our application to Angular v8. We will use the Angular CLI to manage this process for us.

This is the preferred method, as Angular has provided built-in migration scripts or schematics to alleviate some of the manual process involved had we just simply updated versions in our package.json.

Let’s start by running the following command in the terminal:

Update the Global Angular CLI version

npm install -g @angular/cli

Update the core framework and local CLI to v8

ng update @angular/cli @angular/core

Throughout this process, we might encounter issues with third-party libaries. In those instances, it is best to visit the GitHub issues and repositories for those libraries for resolution.

Upgrading NgRx

Now that we have upgraded our application to use Angular v8, let’s proceed with updating NgRx to v8. We will make use of the Angular CLI here as well.

Update NgRx to v8

ng update @ngrx/store

The prior command should update our package.json dependencies and run any NgRx-provided migrations to keep our application in working order.

Depending on your setup, ng update @ngrx/store may not automatically update the additional @ngrx/* libraries that you have installed. If this happens, the best course is to manually run npm install for each additional module in use with NgRx.

Examples are as follows:

npm install @ngrx/[email protected]
npm install @ngrx/[email protected]
npm install @ngrx/[email protected]
npm install @ngrx/[email protected]

NgRx Migration Guide

The NgRx team has provided a detailed migration guide for updating to NgRx v8. More information on upgrading to v8 of NgRx can be found here: V8 Update Guide

Learn by Example - a Fruit Store (NgRx v7)

One of the most popular ways to learn new methods, is through code examples. Let’s explore the following example of a simplified NgRx store that holds an array of Fruit objects.

Each Fruit object consists of three properties fruitId, fruitClass and fruitName.

interface Fruit {
  fruitId: number;
  fruitType: string;
  fruitName: string;
}

For example, if we had an orange, it might look something like this:

const orange: Fruit = {
  fruitId: 1,
  fruitType: 'citrus',
  fruitName: 'orange'
};

State

Exploring further, our State object in the NgRx store will contain properties like fruits, isLoading, and errorMessage.

An example State interface might look like the following:

interface State {
  fruits: Fruit[];
  isLoading: boolean;
  errorMessage: string;
}

An example store with fruits loaded might look like the following:

const state: State = {
  fruits: [
    {
      fruitId: 1,
      fruitType: 'citrus',
      fruitName: 'orange'
    }
  ],
  isLoading: false,
  errorMessage: null
}

Actions

Following proper redux pattern guidance, we cannot directly update state, so we need to define a set of actions to work with our state through a reducer. Let’s imagine we have 3 actions for this example:

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

NgRx v7 Implementation

The actual NgRx v7 implementation of our actions might look something like the following:

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

export enum ActionTypes {
  LOAD_REQUEST = '[App Init] Load Request',
  LOAD_FAILURE = '[Fruits API] Load Failure',
  LOAD_SUCCESS = '[Fruits API] Load Success'
}

export class LoadRequestAction implements Action {
  readonly type = ActionTypes.LOAD_REQUEST;
}

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

export class LoadSuccessAction implements Action {
  readonly type = ActionTypes.LOAD_SUCCESS;
  constructor(public payload: { fruits: Fruit[] }) {}
}

export type ActionsUnion = LoadRequestAction | LoadFailureAction | LoadSuccessAction;

NgRx v8 - Upgrading to createAction

It’s important to note, that while createAction is the hot new way of defining an Action in NgRx, the existing method of defining an enum, class and exporting a type union will still work just fine in NgRx v8.

Beginning with version 8 of NgRx, actions can be declared using the new createAction method. This method is a factory function, or a function that returns a function.

According to the official NgRx documentation, “The createAction function returns a function, that when called returns an object in the shape of the Action interface. The props method is used to define any additional metadata needed for the handling of the action. Action creators provide a consistent, type-safe way to construct an action that is being dispatched.”

In order to update to createAction, we need to do the following steps:

  1. Create a new export const for our action. If our action has a payload, we will also need to migrate to using the props method to define our payload as props.

Example for [App Init] Load Request

// before
export class LoadRequestAction implements Action {
  readonly type = ActionTypes.LOAD_REQUEST;
}
// after
export const loadRequest = createAction('[App Init] Load Request');

Example for [Fruits API] Load Success

// before
export class LoadSuccessAction implements Action {
  readonly type = ActionTypes.LOAD_SUCCESS;
  constructor(public payload: { fruits: Fruit[] }) {}
}
// after
export const loadSuccess = createAction('[Fruits API] Load Success', props<{fruits: Fruit[]}>());
  1. Remove the old action from the ActionTypes enum

  2. Remove the old action from the ActionsUnion

Our final migrated actions file might look something like this:

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

export const loadRequest = createAction('[App Init] Load Request');
export const loadFailure = createAction('[Fruits API] Load Failure', props<{errorMessage: string}>());
export const loadSuccess = createAction('[Fruits API] Load Success', props<{fruits: Fruit[]}>());

As we can see, this is a huge reduction in code, we have gone from 24 lines of code, down to 6 lines of code.

NgRx v8 - Dispatching createAction Actions

A final note is that we need to update the way we dispatch our actions. This is because we no longer need to create class instances, rather we are calling factory functions that return an object of our action.

Our before and after will look something like this:

// before 
this.store.dispatch(new featureActions.LoadSuccessAction({ fruits }))

// after
this.store.dispatch(featureActions.loadSuccess({ fruits }))

Reducer

Continuing with our example, we need a reducer setup to broker our updates to the store. Recalling back to the redux pattern, we cannot directly update state. We must, through a pure function, take in current state, an action, and return a new updated state with the action applied. Typically, reducers are large switch statements keyed on incoming actions.

Let’s imagine our reducer handles the following scenarios:

NgRx v7 Implementation

The actual NgRx v7 implementation of our reducer might look something like the following:

import { ActionsUnion, ActionTypes } from './actions';
import { initialState, State } from './state';

export function featureReducer(state = initialState, action: ActionsUnion): State {
  switch (action.type) {
    case ActionTypes.LOAD_REQUEST: {
      return {
        ...state,
        isLoading: true,
        errorMessage: null
      };
    }
    case ActionTypes.LOAD_SUCCESS: {
      return {
        ...state,
        isLoading: false,
        errorMessage: null,
        fruits: action.payload.fruits
      };
    }
    case ActionTypes.LOAD_FAILURE: {
      return {
        ...state,
        isLoading: false,
        errorMessage: action.payload.errorMessage
      };
    }
    default: {
      return state;
    }
  }
}

NgRx v8 - Upgrading to createReducer

It’s important to note, that while createReducer is the hot new way of defining a reducer in NgRx, the existing method of defining a function with a switch statement will still work just fine in NgRx v8.

Beginning with version 8 of NgRx, reducers can be declared using the new createReducer method.

According to the official NgRx documentation, “The reducer function’s responsibility is to handle the state transitions in an immutable way. Create a reducer function that handles the actions for managing the state using the createReducer function.”

In order to update to createReducer, we need to do the following steps:

  1. Create a new const reducer = createReducer for our reducer.
  2. Convert our switch case statements into on method calls. Please note, the default case is handled automatically for us. The first parameter of the on method is the action to trigger on, the second parameter is a handler that takes in state and returns a new version of state. If the action provides props, a second optional input parameter can be provided. In the example below we will use destructuring to pull the necessary properties out of the props object.
  3. Create a new export function reducer to wrap our const reducer for AOT support.

Once completed, our updated featureReducer will look something like the following:

import { createReducer, on } from '@ngrx/store';
import * as featureActions from './actions';
import { initialState, State } from './state';
...
const featureReducer = createReducer(
  initialState,
  on(featureActions.loadRequest, state => ({ ...state, isLoading: true, errorMessage: null })),
  on(featureActions.loadSuccess, (state, { fruits }) => ({ ...state, isLoading: false, errorMessage: null, fruits })),
  on(featureActions.loadFailure, (state, { errorMessage }) => ({ ...state, isLoading: false, errorMessage: errorMessage })),
);

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

Effects

Because we want to keep our reducer a pure function, it’s often desirable to place API requests into side-effects. In NgRx, these are called Effects and provide a reactive, RxJS-based way to link actions to observable streams.

In our example, we will have an Effect that listens for an [App Init] Load Request Action and makes an HTTP request to our imaginary Fruits API backend.

NgRx v7 Implementation

The actual NgRx v7 implementation of our effect might look something like the following:

@Effect()
  loadRequestEffect$: Observable<Action> = this.actions$.pipe(
    ofType<featureActions.LoadRequestAction>(
      featureActions.ActionTypes.LOAD_REQUEST
    ),
    concatMap(action =>
      this.dataService
        .getFruits()
        .pipe(
          map(
            fruits =>
              new featureActions.LoadSuccessAction({
                fruits
              })
          ),
          catchError(error =>
            observableOf(new featureActions.LoadFailureAction({ errorMessage: error.message }))
          )
        )
    )
  );

NgRx v8 - Upgrading to createEffect

It’s important to note, that while createEffect is the hot new way of defining a reducer in NgRx, the existing method of defining a class property with an @Effect() decorator will still work just fine in NgRx v8.

Beginning with version 8 of NgRx, effects can be declared using the new createEffect method, according to the official NgRx documentation.

In order to update to createEffect, we need to do the following steps:

  1. Import createEffect from @ngrx/effects
  2. Remove the @Effect() decorator
  3. Remove the Observable<Action> type annotation
  4. Wrap this.actions$.pipe(...) with createEffect(() => ...)
  5. Remove the <featureActions.LoadRequestAction> type annotation from ofType
  6. Change the ofType input parameter from featureActions.ActionTypes.LOAD_REQUEST to featureActions.loadRequest
  7. Update the action calls to remove new and to use the creator instead of class instance. For example, new featureActions.LoadSuccessAction({fruits}) becomes featureActions.loadSuccess({fruits}).

Once completed, our updated loadRequestEffect will look something like the following:

  loadRequestEffect$ = createEffect(() => this.actions$.pipe(
    ofType(featureActions.loadRequest),
    concatMap(action =>
    this.dataService
        .getFruits()
        .pipe(
          map(fruits => featureActions.loadSuccess({fruits})),
          catchError(error =>
            observableOf(featureActions.loadFailure({ errorMessage: error.message }))
          )
        )
      )
    )
  );

Conclusion

This brings us to the end of this guide. Hopefully you’ve been able to learn about upgrading your application to Angular v8 and NgRx v8. In addition, you should feel confident in taking advantage of some of the new features available in NgRx v8 to reduce the occurrence of what some might refer to as boilerplate. Happy updating and upgrading!

To learn more techniques, best practices and real-world expert knowledge I’d highly recommend checking out my Angular courses - they will guide you through your journey to mastering Angular to the fullest!

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover