Build better Angular 2+ applications with NGRX State Management

Wojciech Parys
May 11, 2018

How ngrx will save your bandwidth and development process.

In this article we’ll dive into RxJS-powered state management for Angular applications, inspired by Redux. How this approach can improve medium- and large-scale application development process, what are the benefits of using ngrx and what are the core principles behind it.

What’s So Hard About Managing the State?

Let’s take a look at the image below.

Angular A

This is just a typical application. It looks relatively simple, just a few components and services, but as you can see there are a lot of connections. We’ve got a lot of things going on, and the state of our application is actually edited and queried from all over our code. That can lead to issues, because, as our application grows, maintenance and always knowing where new state is added to it, and which services of our app could potentially override existing state, can be difficult. It can be hard to keep track of where the state gets changed and queried in our application. Of course, you can get rid of storing state in the service and call back-end for new data every time when a component is initialized, but it will cost you a lot of bandwidth.

Real Life Problem

At Nexocode we are working on a big Angular application, which has a lot of components and data which is changing all the time. We also deliver our data in real-time connection and in many components we are using same data in particular cases.

For example, we have a component that’s listing many documents, there is also a details page of each document and statistics pages where we need to count some partial data from some documents. Each of them belongs to a user who also has list, details, and statistics. There are also related reviewers and admins, so there are many connections.

So, as you can see, it’s hard to keep track of the app state and we always have to know where our state will change and where we should update it.

Redux to the Rescue!

In the Redux approach you have one central store in your application. So, one central place where you can manage all your application state, the single source of truth. Let’s take a closer look at the image:

Angular B

Your components and services are now calling to one central store. You don’t have to worry about what service you have to provide in a component, or if you should update data only in one service or more. All will be covered by ngrx, so your code will be much cleaner and more readable. You can get rid of all if-statements in your services where you had to check if the data is already loaded or not. Also, if you didn’t store your data before, you will save a lot of bandwidth.

Pros and Cons

  • Immutable data - state is read-only
  • Predictability, maintainability
  • Really helpful dev tools (time traveling)
  • Pure functions update state
  • Single source of truth
  • Universal apps (SSR)
  • Good code separation
  • Fast bug fixing due to 1-4
  • Much better performance with OnPush Strategy
  • More files and code

Your components and services are now calling to one central store. You don’t have to worry about what service you have to provide in a component, or if you should update data only in one service, or more. All will be covered by ngrx, so your code will be much cleaner and more readable. You can get rid of all if statements in your services where you had to check if the data is already loaded or not. Also, if you didn’t store your data before, you will save a lot of bandwidth.

How to start

First you have to install ngrx packages from https://github.com/ngrx/platform:

npm install @ngrx/store @ngrx/effects --save

or if you’re using Yarn

yarn add @ngrx/store @ngrx/effects --save

When you’re done lets create folder structure and first action.

project_root
└───store
│   └───actions
│   └───effects
│   └───reducers
│   └───selectors
│   └───index.ts
├── app.component.ts
├── app.module.ts
import { Action } from '@ngrx/store';  
  
export const LOAD_USERS = '[Users] Load Users';  // It's good practice to add namespace for each action module
export const LOAD_USERS_FAIL = '[Users] Load Users Fail';  
export const LOAD_USERS_SUCCESS = '[Users] Load Users Success';  
  
export class LoadUsers implements Action {  
  readonly type = LOAD_USERS;  
}  
  
export class LoadUsersFail implements Action {  
  readonly type = LOAD_USERS_FAIL;  
  
  constructor(public payload: any) {}  
}  
  
export class LoadUsersSuccess implements Action {  
  readonly type = LOAD_USERS_SUCCESS;  
  
  constructor(public payload: any) {}  
}  
  
// Action types  
export type UsersAction = LoadUsers | LoadUsersFail | LoadUsersSuccess;

Next, according to the image placed above, we have to catch data from action and save it in the store, so let’s create an example reducer:

import * as fromUsers from '../actions/users.action';  
import { Users } from 'resources/users/users';  
  
export interface UsersState {  
  entities: Users.Entities;  
  loaded: boolean,  
  loading: boolean  
}  
  
export const initialState: UsersState = {  
  entities: {},  
  loaded: false,  
  loading: false  
};  
  
export function reducer(  
  state = initialState,  
  action: fromUsers.UsersAction  
): UsersState {  
  switch (action.type) {  
    case fromUsers.LOAD_USERS: {  
      return {  
        ...state,  
        loading: true  
      };  
    }  
  
    case fromUsers.LOAD_USERS_SUCCESS: {  
      const users = action.payload;  
      const entities = users.reduce((entities: Users.Entities, user) => {  
          return {  
            ...entities,  
            [user.id]: user  
          };  
        },  
        {}  
      );
      
      return {  
        ...state,  
        loading: false,  
        loaded: true,  
        entities  
      };  
    }  
  
    case fromUsers.LOAD_USERS_FAIL: {  
      return {  
        ...state,  
        loading: false,  
        loaded: false  
      };  
    }  
  }  
  
  return state;  
}  
  
export const getUsersEntities = (state: UsersState) => state.entities;  
export const getUsersLoaded = (state: UsersState) => state.loaded;  

Now you’re able to dispatch action and store data via ngrx, you can do it by simply writing:

store.dispatch(new <action_name>), for example

import { Store } from '@ngrx/store';
import * as fromStore from '../../store';
export class MyComponent(){
    constructor(private store: Store<fromStore.UsersState>) { }
    loadUsers(){
       this.store.dispatch(new fromStore.LoadUsers());
    }
}

Adding Effects

@ngrx/effects provides an API to model event sources as actions. Effects:

  • Listen for actions dispatched from @ngrx/store.
  • Isolate side effects from components, allowing for more pure components that select state and dispatch actions.
  • Provide new sources of actions to reduce state based on external interactions such as network requests, WebSocket messages and time-based events.

Here is a simple example of using effects in our app:

@Injectable()  
export class UsersEffect {  
  constructor(  
    private actions$: Actions,  
    private usersService: UsersService  
  ) {}  
  
  @Effect()  
  loadUsers$ = this.actions$  
    .ofType(usersActions.LOAD_USERS)  
    .pipe(switchMap(() => {  
        return this.usersService.getUsers().pipe(  
          map((users: Users.List) => new usersActions.LoadUsersSuccess(users)),  
          catchError(error => of(new usersActions.LoadUsersFail(error)))  
        );  
      })  
    );  
  
}  

As you can see, anytime you dispatch LOAD_USERS action, this effect will be triggered and dispatch new action at the end. Remember that effects are Observables, so you have to always return an Observable. Also, at the end you have to dispatch a new action. You can disable it by adding { dispatch: false } to the Effect decorator, for example when you want to log out a user.

Selectors

The last thing about ngrx is selectors. Selectors are pure functions that take slices of state as arguments and return some state data that we can pass to our components. Selectors will return an Observable<T> as a value, so you can use it with async pipe or subscribe for value changes, example below:

this.users$ = this.store.select(fromStore.getAllUsers);
<ul>
    <li *ngFor="let user of $users | async">...</li>
</ul>
this.subscription$ = this.store.select(fromStore.getAllUsers).subscribe(users => {
... // do something with data
this.users = users // just an example
}
//on destroy
this.subscription$.unsubscribe();
<ul>
    <li *ngFor="let user of users">...</li>
</ul>

Remember that when you use subscription, you have to unsubscribe. When you use async approach, Angular will handle unsubscribe for you. Creating new selectors is very simple, a few examples below:

export const getUsersEntities = createSelector(getUsersState, fromUsers.getUsersEntities);  
export const getAllUsers = createSelector(getUsersEntities, (entities) => Object.keys(entities).map(id => entities[id]));  
export const getUsersLoaded = createSelector(getUsersState, fromUsers.getUsersLoaded);  
  
export const getAdminUsers = createSelector(  
  getAllUsers,  
 (users: Users.List): Users.List => users.filter(user => user.isAdmin)
);

You can read more about ngrx here: https://github.com/ngrx/platform/tree/master/docs

Helpful resources:

https://medium.com/ngrx - official ngrx blog
redux devtools - great chrome extension for debugging with time travel
Simple demo app

Now, let's talk about your project!

We don't have one standard offer.
Each project is unique, rest assured that we will approach the next one full of energy and engagement.

LET'S CONNECT