@ngrx/router-store

Angular: The Full Gamut Edition

Charlie Greenman
September 05, 2020
7 min read

Ngrx/store is an Angular flavored Redux styled state management library that leverages Rxjs.

It deals with the management of data streams and propagation of change. It is time bound, meaning that data tracking and histories are stored for referencing and tracing.

Under the traditional model of routing, data is decentralized and sits on each routing state. This means that if the route changes, history of that data is lost. There is no past and future states, only the present and navigating away discards any memory of such occurrences. This can become quite a challenge to keep track of everything in medium to larger sized applications, especially when navigation is expected to occur in high frequency.

Ngrx solves this issue by creating a central storage space for routing. This official library is called ngrx/router-store and is pre-defined to automatically hook into your ngrx/store.

It keeps all your current application's data in one space - turning parts of your browser's memory into a storage bucket for all your data where all mutations occur only through explicit dispatch actions known as reducers and becomes the application's single source of truth. It allows for your application's events to exist in a unified manner rather than decentralized across different parts, children, siblings, partials, factories and routes.

ngrx/router-store, specifically, is the portion of NgRx module that allows for listeners to be used for routing actions, meaning that data is allowed to bestored, shared, consumed and mutated based on the routing status from a single source. @ngrx/router-store, in a way, is like an in-memory database for your application's route related data.

Why do we need it?

When data is decentralized and exists on the fly, it becomes prone to errors dueto a lack of history tracking and mutations can occur from different directions. Duplication can accidentally happen as we try to replicate certain data indifferent states and parts of the application.

When relying on Angular's routing system, we rely on data persistence through params from navigation/router state. If a child or sibling component requires that data, it becomes coupled with the parent and data needs to be presented again in order to be consumed. While factory patterns may solve this issue, it can quickly get messy if external entry is granted without explicit knowledge.

In larger teams, factory patterns may not be enough to control the flow and history of data and human error may introduce inconsistencies in the code.

ngRx solves this, along with the reduction of time and code overheads needed to create factory patterns and singular storage spaces. The library comes ready to be plugged into any Angular application with its own set of Redux inspired approach to centralized state storage. Each cycle in a router-store captures a snapshot of the route's state and its associated data. When data is decoupled from routing, it allows your application to become more agile and less dependent on data states through route params.

How to install router store

Once you have your Angular app, you can use npm to install router-store by using the following commands:

npm install @ngrx/router-store –save

If you're using yarn:

yarn add @ngrx/router-store

If you're project is created with Angular CLI version 6+, you can use thefollowing command:

ng add @ngrx/router-store

To check your Angular CLI version, use the following command:

ng --version

To use inside your application, you'll need to import the StoreRouterConectingModule and routerReducer from @ngrx/router-store like so:

import { StoreRouterConnectingModule, routerReducer } from '@ngrx/router-store';

Router actions and why they might be useful

An action is anything that you can do to your application. A router action is an event that can occur against a specific route on your Angular app. When it comes to routing, are 5 specific actions that can occur and they are the request, the action of navigation, the aftermath (also known as navigated), cancellation and navigation error.

Being able to access and track these actions allows you to control the flow of route state storage management, access to data and the life cycle process. When used in conjunction with route guards - a feature that allows you to protect your views from rendering when there isn't enough information or the right access permissions - router actions can help with the resolutions of states and its consumption.

A request action always kickstart the process which then runs the navigation action that determines if the dispatch should occur or not. If guards are valid,a successful navigation will occur and result in a router navigated action. If something went wrong due to exceptions or lack of user permissions, the router action will return a ROUTER\_CANCEL action and nullify any attempts toaccess the route.

A ROUTER\_ERROR action may occur during the navigation life cycle andreturns the stored state before navigation occurred. This is particularly useful as it allows the application to back track its action and restore its former data -- a sort of back button without the need for extra configuration or callto the router state bucket.

How to use a custom serializer

A custom serializer prevents the mutation of snapshot data during the dispatchprocess. As data during the navigation cycle is prone to mutability, a customserializer returns only what you need to be added to the payload and store. Soin essence, it tracks the difference and change of a particular state withoutmodifying the entire stored state snapshot.

A custom serializer can be implemented through the abstract classRouterStateSerializer. It is, in a way, a middleman class that processes thedifference between what the current state is, what is to be changed and updatesonly what is necessary.

To create a custom serializer, you will need to import Params andRouterStateSnapshot from \@angular/router, along with RouterStateSerializer from\@ngrx/router-store

import { Params, RouterStateSnapshot } from '@angular/router';
import { RouterStateSerializer } from '@ngrx/router-store';

To create a custom serializer, export a class that implementsRouterStateSerializer with an interface to ensure object uniformity.

Using the serialize() method to convert the state object into a unified format that conforms to your application's requirements. This often comes in the form of mapping router state values to a predefined interface that may look something like this:

//to be used by the serialization process
export interface RouterStateUrl {
  url: string;
  params: Params;
  queryParams: Params;
}

The CustomSerializer class that implements the imported RouterStateSerializerusing the RouterStateUrl interface created.

export class CustomSerializer implements RouterStateSerializer<RouterStateUrl> {
   //serialization code here
}

Set up the serialization method to return a uniformed set of parameters basedon the template set in the RouterStateUrl interface.

serialize(routerState: RouterStateSnapshot): RouterStateUrl {
    let route = routerState.root;

    while (route.firstChild) {
      route = route.firstChild;
    }

    const {
      url,
      root: { queryParams },
    } = routerState;
    const { params } = route;

    // returning the object based on the RouterStateUrl interface
    return { url, params, queryParams };
  }

To use the custom serializer, implement it inside your \@NgModule and call yourexported CustomSerializer class inside the StoreRouterConnectingModule.

@NgModule({
  imports: [
    StoreModule.forRoot(
      { router: routerReducer },
    ),
    RouterModule.forRoot([
      // routes
    ]),
    StoreRouterConnectingModule.forRoot({
      serializer: CustomSerializer,
    }),
  ],
})

Benefits of using router-store with ngrx/store-freeze

ngrx/store-freeze is a dev tool that can be used during the development phase of an Angular application to prevent state mutation when usingrouter-store. It sits on top of router-store as meta data that acts as an insurance against changes in state data during the process of transfer to state storage.

As router-store provides snapshots of the RouterState during the navigation life cycle, it is vital that snapshots passed do not change during the process of dispatch as this will result the store cycle's truthiness breaking due to inaccurate snapshot data.

While serialization already prevents this, store-freeze acts as an additional safeguard with exceptions thrown when mutations do occur at runtime. It automatically 'deep freezes' the entire store state object and dispatch actions, resulting in a read only effect before it gets passed to the serializer. This allows errors to be caught before it gets dispatched, serialized and passed into storage.

Subscribe to the Razroo Angular Newsletter!

Razroo takes pride in it's Angular newsletter, and we really pour heart and soul into it. Pass along your e-mail to recieve it in the mail. Our commitment, is to keep you up to date with the latest in Angular, so you don't have to.

More articles similar to this

footer

Razroo is committed towards contributing to open source. Take the pledge towards open source by tweeting, #itaketherazroopledge to @_Razroo on twitter. One of our associates will get back to you and set you up with an open source project to work on.