Brian Love
Angular + TypeScript Developer in Denver, CO

Updating To NgRx 4

Reading time ~18 minutes

Here’s a quick guide to updating your code to use NgRx 4.

In case you missed it, NgRx 4 dropped on July 18. I’m going to review some of the changes that I had to implement in my codebase to update to this latest release.

TypeScript v2.4+

First, you need to ensure that you are using version 2.4+ of TypeScript. Update your project’s package.json file to:

{
  "devDependencies": {
    "typescript": "^2.5.2"
  }
}

Make sure you remove your project’s TypeScript installation, and then run npm install:

$ rm -rf ./node_modules/typescript
$ npm install

RxJS v5.4+

Next, ensure that you have installed the latest version of RxJS. Update your project’s package.json file to:

{
  "dependencies": {
    "rxjs": "^5.4.3"
  }
}

Again, make sure you remove your project’s RxJS installation, and then run npm install:

$ rm -rf ./node_modules/rxjs
$ npm install

Actions

The way you define your actions using NgRx v4 is not that much different that how you defined actions using v2/3. Let’s quickly review how we define actions:

// @ngrx
import { Action } from "@ngrx/store";

// models
import { Activity } from "../../models/activity";

// activities
export const LOAD_ACTIVITY = "[activities] Load activity";
export const LOAD_ACTIVITY_ERROR = "[activities] Load activity error";
export const LOAD_ACTIVITY_SUCCESS = "[activities] Load activity success";

/**
 * Load activity.
 * @class LoadActivityAction
 * @implements {Action}
 */
export class LoadActivityAction implements Action {
  readonly type = LOAD_ACTIVITY;
  constructor(public payload: { id: string }) {}
}

/**
 * Load activity error.
 * @class LoadActivityErrorAction
 * @implements {Action}
 */
export class LoadActivityErrorAction implements Action {
  readonly type = LOAD_ACTIVITY_ERROR;
  constructor(public payload: { error: Error }) {}
}

/**
 * Load activity success.
 * @class LoadActivitySuccessAction
 * @implements {Action}
 */
export class LoadActivitySuccessAction implements Action {
  readonly type = LOAD_ACTIVITY_SUCCESS;
  constructor(public payload: { activity: Activity }) {}
}

/**
 * Actions type.
 * @type {Actions}
 */
export type Actions
  = LoadActivityAction
  | LoadActivityErrorAction
  | LoadActivitySuccessAction;

Above is an example of three actions that load an activity (from a REST API). Along with the LoadActivityAction, I also define LoadActivityErrorAction and LoadActivitySuccessAction actions for when an exception occurs or when the activity is successfully loaded.

A few things to note:

  • First, we import the Action interface from @ngx/store.
  • Next, we define constant string values for each action.
  • Then, we define an action class that implements the Action interface for each action. The public type parameter is read-only, and is set to the value of the constant string. We also define the constructor() function for each class, which contains a publicly available payload property. Each payload is an Object with the necessary properties for the action.

This should look very familiar to your NgRx v2/3 code, as nothing has changed.

Next, let’s look at the changes to how we navigate using NgRx v4. The first thing you will notice is that the navigation functions are gone. This includes: go(), back() and forward() functions defined in the @ngrx/router-store module.

So, what do we do?

Simple. We will create our own actions.

First, create a new src/app/actions/router.ts file in your application:

$ cd src/app
$ mkdir actions
$ touch router.ts

Here is the full contents of my router.ts file:

// ngrx
import { Action } from "@ngrx/store";
import { NavigationExtras } from "@angular/router";

export const GO = "[Router] Go";
export const BACK = "[Router] Back";
export const FORWARD = "[Router] Forward";

export class Go implements Action {
  readonly type = GO;

  constructor(public payload: {
    path: any[];
    query?: object;
    extras?: NavigationExtras;
  }) {}
}

export class Back implements Action {
  readonly type = BACK;
}

export class Forward implements Action {
  readonly type = FORWARD;
}

export type Actions
  = Go
  | Back
  | Forward;

This should look very familiar to the actions that we defined previously. In this case, we are defining three new action classes: Go, Back and Forward.

Note that the Go class’s payload has three properties defined:

  • path: specify an array of commands that match Angular’s Router.navigate() method.
  • query: specify an object of name/value pairs for query params, similar to Angular’s Params type
  • extras: specify an object of NavigationExtras

Note that the query and extras properties are optional.

Next, we need to define side effects for each of these navigation classes. To do so, create a new src/app/effects/router.ts file:

$ cd src/app
$ mkdir effects
$ touch router.ts

Here is the full contents of the src/app/effects/router.ts file:

import { Injectable } from "@angular/core";
import { Router } from "@angular/router";
import { Location } from "@angular/common";
import { Effect, Actions } from "@ngrx/effects";
import "rxjs/add/operator/do";
import "rxjs/add/operator/map";
import * as RouterActions from "../actions/router";

@Injectable()
export class RouterEffects {

  @Effect({ dispatch: false })
  navigate$ = this.actions$.ofType(RouterActions.GO)
    .map((action: RouterActions.Go) => action.payload)
    .do(({ path, query: queryParams, extras}) => this.router.navigate(path, { queryParams, ...extras }));

  @Effect({ dispatch: false })
  navigateBack$ = this.actions$.ofType(RouterActions.BACK)
    .do(() => this.location.back());

  @Effect({ dispatch: false })
  navigateForward$ = this.actions$.ofType(RouterActions.FORWARD)
    .do(() => this.location.forward());

  constructor(
    private actions$: Actions,
    private router: Router,
    private location: Location
  ) {}
}

A few things to note:

  • First, the RouterEffects class is decorated with the @Injectable() decorator.
  • Next, we define a property for each navigation action. Each property is decorated with the @Effect() decorator. We set the dispatch property value to false in the decorator to note that the effect will not dispatch any new actions.
  • Within each property we invoke the appropriate method on either the Router or on the Location instance that is injected via the constructor() function.
  • We use the do() RxJs method to transparently perform a side effect. You can learn more about the do() utility method at learnrxjs.io.
  • Also, note the use of the map() operator to extract the payload object that is provided with the Go class’s constructor function.

Ok, so now let’s look at the implementation. Here is what our navigation implementation was previously:

import { go } from "@ngrx/router-store";

public editActivity(activity: Activity) {
  this.store.dispatch(go(["/activities/edit", activity._id]));
}

Here is the refactored navigation using the new Go action:

import { Go } from "../actions/router";

public editActivity(activity: Activity) {
  this.store.dispatch(new Go({
    path: ["/activities/edit", activity._id]
  }));
}

A few things to note:

  • First, we no longer import the go() function from the @ngrx/router-store module. Instead, we import the Go class from the router module we previously defined.
  • We still use the Store.dispath() method to dispatch the action.
  • We dispatch a new instance of the Go class, specifying the payload object. In this example, I am specifying only the path property. Note that the value of the path is the same as before.

Effects

The effects API for registering effects has been updated. Previously you had to invoke the run() function for each effects class in your application. This has been deprecated in favor of two new functions: forRoot() and forFeature(). The forRoot() function is invoked in your root AppModule, and forFeature() is invoked in any subsequent feature modules.

For example, before my application’s AppModule class decorator was:

import { EffectsModule } from "@ngrx/effects";

@NgModule({
  // code omitted
  imports: [
    EffectsModule.run(ActivityEffects),
    EffectsModule.run(UserEffects)
  ]
})
export class AppModule { }

Now, it’s been updated to use the forRoot() function:

import { EffectsModule } from "@ngrx/effects";

import { RouterEffects } from "./effects/router";

@NgModule({
  // code omitted
  imports: [
    EffectsModule.forRoot([
      RouterEffects
    ])
  ]
})
export class AppModule { }

Note that the only effects class that I am configuring in my AppModule is the RouterEffects. While previously I had configured all of the effects classes for my entire app. Your application may be structured different than mine, but I think this is worth noting. I am now wiring up my effects in each lazy-loaded module as appropriate, rather than in the root AppModule.

Typed Payload

The @ngrx/effects module included a helper toPayload() function that was used in conjuction with the map() function to return the payload for the current action. While this was helpful, the issue was that the function returns any.

Here was the signature for the function:

export declare function toPayload(action: Action): any;

This is an example of how it was used:

@Effect()
public delete: Observable<Action> = this.actions
  .ofType(DELETE_ACTIVITY)
  .map(toPayload)
  .switchMap(payload => {
    return this.activityService.delete(payload.activity)
      .map(() => new DeleteActivitySuccessAction({ activity: payload.activity }))
      .catch(error => Observable.of(new DeleteActivityErrorAction({ error: error })));
  });

In the example above, note the use of the toPayload() function. This will return an observable with just the payload for the current action, in this case the DELETE_ACTIVITY action.

The issue is that the payload object that is passed to the switchMap() function above is of type any. This means we have no type safety, and we do not get any help in our editor about what properties the payload object has.

Here is an example of using a typed payload with NgRx v4:

@Effect()
public delete: Observable<Action> = this.actions
  .ofType(DELETE_ACTIVITY)
  .map((action: DeleteActivityAction) => action.payload)
  .switchMap(payload => {
    return this.activityService.delete(payload.activity)
      .map(() => new DeleteActivitySuccessAction({ activity: payload.activity }))
      .catch(error => Observable.of(new DeleteActivityErrorAction({ error: error })));
  });

We simply use a fat arrow function to specify the type of the action, in this case DeleteActivitySuccessAction, which returns the payload from the action. Now, the payload argument to the switchMap() method includes type safety.

Reducers

A lot changed in how you configure and combine your application’s reducers in NgRx v4. The good thing is that nothing will change in your pure reducer() functions.

I’m not sure if I was following best practices or not, but previously I had set up a single src/app/app.reducers.ts file for combining all of my reducers for my application:

import { createSelector } from "reselect";

// import @ngrx
import { ActionReducer, combineReducers } from "@ngrx/store";
import { compose } from "@ngrx/core/compose";
import { routerReducer, RouterState } from "@ngrx/router-store";
import { storeFreeze } from "ngrx-store-freeze";

// import environment
import { environment } from "../environments/environment";

/**
 * Every reducer module"s default export is the reducer function itself. In
 * addition, each module should export a type or interface that describes
 * the state of the reducer plus any selector functions. The `* as`
 * notation packages up all of the exports into a single object.
 */
import * as activities from "./activities/activities.reducers";
import * as users from "./users/users.reducers";

/**
 * We treat each reducer like a table in a database.
 * This means our top level state interface is just a map of keys to inner state types.
 */
export interface State {
  activities: activities.State;
  router: RouterState;
  users: users.State;
}

/**
 * Because metareducers take a reducer function and return a new reducer,
 * we can use our compose helper to chain them together. Here we are
 * using combineReducers to make our top level reducer, and then
 * wrapping that in storeLogger. Remember that compose applies
 * the result from right to left.
 */
const reducers = {
  activities: activities.reducer,
  router: routerReducer,
  users: users.reducer
};

// development reducer includes storeFreeze to prevent state from being mutated
const developmentReducer: ActionReducer<State> = compose(storeFreeze, combineReducers)(reducers);

// production reducer
const productionReducer: ActionReducer<State> = combineReducers(reducers);

/**
 * The single reducer function.
 * @function reducer
 * @param {any} state
 * @param {any} action
 */
export function reducer(state: any, action: any) {
  if (environment.production) {
    return productionReducer(state, action);
  } else {
    return developmentReducer(state, action);
  }
}

/**********************************************************
 * Activities Reducers
 *********************************************************/

/**
 * Returns the activities state
 */
export const getActivitiesState = (state: State) => state.activities;

/**
 * Returns the activities.
 */
export const getActivities = createSelector(getActivitiesState, activities.getActivities);

/**
 * Returns the activity.
 */
export const getActivity = createSelector(getActivitiesState, activities.getActivity);

/**********************************************************
 * Users Reducers
 *********************************************************/

/**
 * Returns the user state.
 */
export const getUsersState = (state: State) => state.users;

/**
 * Returns the authenticated user
 */
export const getAuthenticatedUser = createSelector(getUsersState, users.getAuthenticatedUser);

/**
 * Returns the authentication error.
 */
export const getAuthenticationError = createSelector(getUsersState, users.getAuthenticationError);

/**
 * Returns true if the user is authenticated
 */
export const isAuthenticated = createSelector(getUsersState, users.isAuthenticated);

Wow, that’s long! And the worst part is, that is just a small part of my application’s reducer functions. I would guess there are over 100 functions that I had previously defined in this reducers file.

The first thing I did was to delete this file.

Then, following the example application architecture I started by creating a reducers directory in each module.

Let’s focus on my src/app/activities/reducers directory. In here, I have three files:

  1. src/app/activities/reducers/activities.ts
  2. src/app/activities/reducers/index.ts
  3. src/app/activities/reducers/modules.ts

In this modules I have multiple state objects: activities and modules. This takes advantage of NgRx’s fractal state management:

Store uses fractal state management, which provides state composition through feature modules, loaded eagerly or lazily.

Let’s look at the ActivitiesState and reducer in src/app/activities/reducers/activities.ts:

// import actions
import {
  LOAD_ACTIVITY,
  LOAD_ACTIVITY_ERROR,
  LOAD_ACTIVITY_SUCCESS,
  Actions
} from "../actions/activity";

// import models
import { Activity } from "../../models/activity";

/**
 * The state.
 */
export interface State {
  activity?: Activity;
  error?: Error;
  loading: boolean;
}

/**
 * The initial state.
 */
const initialState: State = {
  loading: false,
};

/**
 * The reducer function.
 */
export function reducer(state: State = initialState, action: Actions): State {
  switch (action.type) {
    case LOAD_ACTIVITY:
      return { ...state, ...{
        activity: undefined,
        error: undefined,
        loading: true
      }};

    case LOAD_ACTIVITY_ERROR:
      return { ...state, ...{
        error: action.payload.error,
        loading: false
      }};

    case LOAD_ACTIVITY_SUCCESS:
      return { ...state, ...{
        activity: action.payload.activity,
        loading: false
      }};

    default:
      return state;
  }
}

/**
 * Return true if the activity is loading
 */
export const isLoading = (state: State) => state.loading;

/**
 * Return the activity
 */
export const getActivity = (state: State) => state.activity;

A few things to note here:

  • First, we import the action constant strings along with the Actions type for this feature.
  • Next, we define the State interface.
  • Then, we define our initialState object that implements the State interface.
  • Then, we define the pure reducer() function that modifies our state based on the dispatched action to the store.
  • Finally, we define state selector functions that return the value of the loading boolean value, and the activity that has been loaded.

This should look very familiar, as this is exactly what we did using NgRx v2/3.

Now, for clarity, let’s look at the src/app/activities/reducers/modules.ts file:

// import actions
import {
  LOAD_ACTIVITIES_FOR_MODULE,
  LOAD_ACTIVITIES_FOR_MODULE_ERROR,
  LOAD_ACTIVITIES_FOR_MODULE_SUCCESS,
  Actions
} from "../actions/module";

// import models
import { Activity } from "../../models/activity";
import { Module } from "../../models/module";

/**
 * The state.
 */
export interface State {
  activities?: Activity[];
  error?: Error;
  loading: boolean;
  module?: Module;
}

/**
 * The initial state.
 */
const initialState: State = {
  activities: [],
  loading: false
};

/**
 * The reducer function.
 */
export function reducer(state: State = initialState, action: Actions): State {
  switch (action.type) {
    case LOAD_ACTIVITIES_FOR_MODULE:
      return { ...state, ...{
        activities: [],
        error: undefined,
        module: action.payload.module
      }};

    case LOAD_ACTIVITIES_FOR_MODULE_ERROR:
      return { ...state, ...{
        error: action.payload.error,
        loading: false
      }};

    case LOAD_ACTIVITIES_FOR_MODULE_SUCCESS:
      return { ...state, ...{
        activities: action.payload.activities,
        loading: false
      }};

    default:
      return state;
  }
}

/**
 * Return true if the activities are loading
 */
export const areActivitiesLoading = (state: State) => state.loading;

/**
 * Return activities.
 */
export const getActivities = (state: State) => state.activities;

This not very different from the state management for our activities. In this case, I am managing the state of activities for a specific module. While, this is specific to my application, I think this will be helpful to show how we compose multiple state managements

Now, let’s look at our state composition in the src/app/activities/reducers/index.ts file:

// ngrx
import { createSelector, createFeatureSelector } from "@ngrx/store";

// reducers
import { State as RootState } from "../../reducers";
import * as fromActivities from "./activities";
import * as fromModules from "./modules";

export interface ActivitiesState {
  activities: fromActivities.State;
  modules: fromModules.State;
}

export interface State extends RootState {
  "activities": ActivitiesState;
}

export const reducers = {
  activities: fromActivities.reducer,
  modules: fromModules.reducer
};

/**
 * The createFeatureSelector function selects a piece of state from the root of the state object.
 * This is used for selecting feature states that are loaded eagerly or lazily.
*/
export const getActivitiesState = createFeatureSelector<ActivitiesState>("activities");

/**
 * Every reducer module exports selector functions, however child reducers
 * have no knowledge of the overall state tree. To make them useable, we
 * need to make new selectors that wrap them.
 *
 * The createSelector function creates very efficient selectors that are memoized and
 * only recompute when arguments change. The created selectors can also be composed
 * together to select different pieces of state.
 */
export const getActivityEntityState = createSelector(
  getActivitiesState,
  (state: ActivitiesState) => state.activities
);

export const getModulesEntityState = createSelector(
  getActivitiesState,
  (state: ActivitiesState) => state.modules
);

/**
 * Returns the activity.
 */
export const getActivity = createSelector(getActivityEntityState, fromActivities.getActivity);

/**
 * Returns the module activities.
 */
export const getModuleActivities = createSelector(getModulesEntityState, fromModules.getActivities);

Let’s review our state composition:

  • First, we import the createSelector() and createFeatureSelector() functions from the @ngrx/store module.
  • Next, we import our root State interface that is defined in src/app/reducers/index.ts. We will take a look at this next.
  • Then, we import each fractal state management reducer. In this case I am importing all exported members from each module: src/app/activities/reducers/activities.ts and src/app/activities/reducers/modules.ts.
  • Now we can define our composed ActivititeState interface. I am calling it this because this is for the ActivitiesModule module in my application, which is lazy-loaded.
  • Next, we define a State interface that extends the RootState, specifying the feature state.
  • Then, we define a reducers object that contains both reducer functions for this module. We defined these previously.
  • Then, using the createFeatureSelector() convenience method we create the feature state.
  • Finally, we define selectors using createSelector() convenience method. Note that we first specify the appropriate feature selector function, and then we specify the selector functions that we defined in each module.

I should also make sure to mention that we are no longer dependent on the createSelector() method in the reselect module. You can safely remove this dependency from your package.json file after upgrading to NgRx v4.

Now, let’s finish upgrading our reducers by defining the root State interface. We’ll be using the @ngrx/router-store to bind Angular’s router to our store.

First, I created a new src/app/reducers/index.ts file:

$ cd src/app
$ mkdir reducers
$ touch index.ts

Here is the full contents of the src/app/reducers/index.ts file:

// import @ngrx
import {
  ActionReducer,
  ActionReducerMap,
  createSelector,
  createFeatureSelector,
  MetaReducer
} from "@ngrx/store";
import { RouterReducerState, routerReducer } from "@ngrx/router-store";
import { RouterStateUrl } from "../shared/utils";

// import environment
import { environment } from "../../environments/environment";

/**
 * As mentioned, we treat each reducer like a table in a database. This means
 * our top level state interface is just a map of keys to inner state types.
 */
export interface State {
  routerReducer: RouterReducerState<RouterStateUrl>;
}

/**
 * Our state is composed of a map of action reducer functions.
 * These reducer functions are called with each dispatched action
 * and the current or initial state and return a new immutable state.
 */
export const reducers: ActionReducerMap<State> = {
  routerReducer: routerReducer
};


// log all actions
export function logger(reducer: ActionReducer<State>): ActionReducer<State> {
  return function(state: State, action: any): State {
    console.log("state", state);
    console.log("action", action);
    return reducer(state, action);
  };
}

/**
 * By default, @ngrx/store uses combineReducers with the reducer map to compose
 * the root meta-reducer. To add more meta-reducers, provide an array of meta-reducers
 * that will be composed to form the root meta-reducer.
 */
export const metaReducers: MetaReducer<State>[] = !environment.production
? [logger]
: [];

I can’t take any credit for this, as I took this straight from the example application provided with NgRx v4.

Now, let’s go back to our AppModule and look at our necessary changes. Before, we wired up the router and store as follows:

import { RouterStoreModule } from "@ngrx/router-store";
import { StoreModule } from "@ngrx/store";

import { reducer } from "./app.reducers";

@NgModule({
  // code omitted
  imports: [
    RouterStoreModule.connectRouter(),
    StoreModule.provideStore(reducer, {
      router: window.location.pathname + window.location.search
    }),
  ]
})
export class AppModule { }

After our upgrade to NgRx v4 our AppModule should look like:

import { StoreRouterConnectingModule } from "@ngrx/router-store";
import { StoreModule } from "@ngrx/store";

import { metaReducers, reducers } from "./reducers";

@NgModule({
  // code omitted
  imports: [
    StoreRouterConnectingModule,
    StoreModule.forRoot(reducers, { metaReducers }),
  ]
})
export class AppModule { }

A few things to note:

  • First, we import the new StoreRouterConnectingModule module from @ngrx/router-store. The RouterStoreModule module has been deprecated in NgRx v4.
  • Second, we no longer import the single reducer function. If you recall from my v2/3 code (and from the v2/3 example app) we used either the compose() function when in development, or the combineReducers() function when in production to create a single reducer function. This was passed into the StoreModule.provideStore method, which has also been deprecated in v4.
  • Next, we import the metaReducers and reducers from our new src/app/reducers/index.ts module.
  • Then, we specify the StoreRouterConnectingModule module in the imports array. Note that we just import the module. We do not need to invoke the connectRouter() method like we did previously; again, this as been deprecated in v4.
  • Finally, we invoke the forRoot() method on the StoreModule class, specifying our reducers.

Note that we use the forRoot() method in this instance because this is our root AppModule. Let’s quickly review how we use the forFeature() method in a feature module.

Here is a snippet from the ActivitiesModule in src/app/activities/activities.module.ts:

import { EffectsModule } from "@ngrx/effects";
import { StoreModule } from "@ngrx/store";

import { ActivityEffects } from "./effects";
import { reducers } from "./reducers";

@NgModule({
  // code omitted

  imports: [
    EffectsModule.forFeature([
      ActivityEffects
    ]),
    StoreModule.forFeature("activities", reducers)
  ]
})
export class ActivitiesModule { }

A few things to note:

  • First, we import the EffectsModule and StoreModule from @ngrx/effects and @ngrx/store, respectively.
  • Then, we import the ActivitiesEffects class. These are the side effects that we defined for this feature module.
  • Then, we import the reducers object that contains each state’s reducer function.
  • Then, we invoke the EffectsModule.forFeature() method, providing an array of effects for this feature.
  • Finally, we invoke the StoreModule.forFeature() method, specifying the name of the feature state, and then the reducers for the state.

Conclusion

In conclusion, I highly recommend you check out some of the resources provided by the NgRx team:

Brian Love

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Denver and I ski (a lot).