Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

Route Params with NgRx Store

Learn to access route parameters with NgRx @ngrx/router-store.

Use Case

While I have used the NgRx store module for redux-inspired state management in Angular in many applications, I have often not found a use case for using the @ngrx/router-store package.

The purpose of the @ngrx/router-store package is to provide:

Bindings to connect Angular Router with Store.

The specific use case that I recently discovered for using this package is an application with:

Selectors

NgRx includes selectors to access the current route URL, it’s route parameters, query parameters, and fragment. There are also factory selectors to access specific parameters by name.

We access the selectors via the getSelectors() function:

export const routerState = createFeatureSelector<
  RouterState,
  RouterReducerState<any>
>('router');

export const {
  selectCurrentRoute, // select the current route
  selectFragment, // select the current route fragment
  selectQueryParams, // select the current route query params
  selectQueryParam, // factory function to select a query param
  selectRouteParams, // select the current route params
  selectRouteParam, // factory function to select a route param
  selectRouteData, // select the current route data
  selectUrl, // select the current url
} = getSelectors(routerState);

Use with Effects

While we can use the selectors to access route parameters in a component, I don’t find that particularly helpful compared to simply injecting the ActivatedRoute class. Rather, I have found that using the route parameter selectors very helpful when reacting to navigation events via an effect.

Let’s take a look at a simple effect that opens the user’s account information in a modal when the #account fragment is in the URL:

openAccountModal$ = createEffect(() =>
  this.actions$.pipe(
    ofType(routerNavigatedAction),
    withLatestFrom(this.store.pipe(select(selectRouteFragment))),
    filter(([, fragment]) => fragment && fragment === 'account'),
    map(() =>
      openModal({
        config: new AccountModal(),
      }),
    ),
  ),
);

The key takeaways of the code above are:

Some advantages of this approach:

Accessing All Parameters

While I find that the current implementation of NgRx’s selectors complete for my use case, you may discover that the selectors only provide access to the last child route’s parameters as reported in this issue.

We can easily write some helper functions to create a Map() of all route parameters:

private getAllRouteParameters(snapshot: SerializedRouterStateSnapshot) {
  let route = snapshot.root;
  let params = new Map(Object.keys(route.params).map(key => [key, route.params[key]]));
  while (route.firstChild) {
    route = route.firstChild;
    Object.keys(route.params).forEach(key => params.set(key, route.params[key]));
  }
  return params;
}

private getAllQueryParameters(snapshot: SerializedRouterStateSnapshot) {
  let route = snapshot.root;
  let params = new Map(Object.keys(route.queryParams).map(key => [key, route.queryParams[key]]));
  while (route.firstChild) {
    route = route.firstChild;
    Object.keys(route.queryParams).forEach(key => params.set(key, route.queryParams[key]));
  }
  return params;
}

We can then use these helper methods in an effect by providing the routeState snapshot:

test$ = createEffect(
  () =>
    this.actions$.pipe(
      ofType(routerNavigatedAction),
      tap(({ payload }) => {
        console.log(this.getAllRouteParameters(payload.routerState));
        console.log(this.getAllQueryParameters(payload.routerState));
      }),
    ),
  { dispatch: false },
);

In the effect above, I’m logging out all of the route and query parameters. For the sake of brevity, this effect is just a test and does not dispatch a subsequent action.

Conclusion

In conclusion, I found that the provided selectors in the @ngrx/router-store package sufficient for my use case, however, if you find that you need a way to access all route parameters and query parameters, I’ve provided some helpful methods for you that you can use in your effect class.