Brian F Love
Learn from a Google Developer Expert focused on Angular, Web Technologies, and Node.js from Portland, OR.
Ad·ultimatecourses.com
Learn Angular the right way with Ultimate Courses

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:

  • Well defined route parameters, query parameters, and fragments.
  • Application state is derived from route.
  • Application state is persisted into the route.

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:

  • We are using NgRx version 8's createEffect() function to create an effect.
  • Using the withLatestFrom() RxJS operator we can access the selected route fragment from the store using the selectRouteFragment operator.
  • We filter() for a fragment whose value is the string account.
  • We then map() to the openModal() action that is dispatched to open the AccountModal.

Some advantages of this approach:

  • I can have "global" effects that react to navigation events and route parameters, query parameters, and fragment values.
  • I wouldn't want to put this logic in a component.
  • Without NgRx I could wire this up in a service that opened the modal, however, I find this approach to be very clean when using NgRx.

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.

Brian F 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 Portland and I ski (a lot).