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 theselectRouteFragment
operator. - We
filter()
for a fragment whose value is the stringaccount
. - We then
map()
to theopenModal()
action that is dispatched to open theAccountModal
.
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.