Navigation actions are not provided as part of the router package. You provide your own
custom navigation actions that use the Router
within effects to navigate.
import { Action } from '@ngrx/store';
import { NavigationExtras } from '@angular/router';
export const GO = '[Router] Go';
export const BACK = '[Router] Back';
export const FORWARD = '[Router] Forward';
export class Go implements Action {
readonly type = GO;
constructor(
public payload: {
path: any[];
query?: object;
extras?: NavigationExtras;
}
) {}
}
export class Back implements Action {
readonly type = BACK;
}
export class Forward implements Action {
readonly type = FORWARD;
}
export type RouterActionsUnion = Go | Back | Forward;
import * as RouterActions from './actions/router';
store.dispatch(new RouterActions.Go({
path: ['/path', { routeParam: 1 }],
query: { page: 1 },
extras: { replaceUrl: false }
});
store.dispatch(new RouterActions.Back());
store.dispatch(new RouterActions.Forward());
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Location } from '@angular/common';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { map, tap } from 'rxjs/operators';
import * as RouterActions from './actions/router';
@Injectable()
export class RouterEffects {
@Effect({ dispatch: false })
navigate$ = this.actions$.pipe(
ofType(RouterActions.GO),
map((action: RouterActions.Go) => action.payload),
tap(({ path, query: queryParams, extras }) =>
this.router.navigate(path, { queryParams, ...extras })
)
);
@Effect({ dispatch: false })
navigateBack$ = this.actions$.pipe(
ofType(RouterActions.BACK),
tap(() => this.location.back())
);
@Effect({ dispatch: false })
navigateForward$ = this.actions$.pipe(
ofType(RouterActions.FORWARD),
tap(() => this.location.forward())
);
constructor(
private actions$: Actions,
private router: Router,
private location: Location
) {}
}
During each navigation cycle, a RouterNavigationAction
is dispatched with a snapshot of the state in its payload, the RouterStateSnapshot
. The RouterStateSnapshot
is a large complex structure, containing many pieces of information about the current state and what's rendered by the router. This can cause performance
issues when used with the Store Devtools. In most cases, you may only need a piece of information from the RouterStateSnapshot
. In order to pare down the RouterStateSnapshot
provided during navigation, you provide a custom serializer for the snapshot to only return what you need to be added to the payload and store.
Additionally, the router state snapshot is a mutable object, which can cause issues when developing with store freeze to prevent direct state mutations. This can be avoided by using a custom serializer.
NOTE: To use the time-travelling debugging in the Devtools with router-store, you must return an object containing a url
property when using the routerReducer
.
import { StoreModule, ActionReducerMap } from '@ngrx/store';
import { Params, RouterStateSnapshot } from '@angular/router';
import {
StoreRouterConnectingModule,
routerReducer,
RouterReducerState,
RouterStateSerializer,
} from '@ngrx/router-store';
export interface RouterStateUrl {
url: string;
params: Params;
queryParams: Params;
}
export interface State {
router: RouterReducerState<RouterStateUrl>;
}
export class CustomSerializer implements RouterStateSerializer<RouterStateUrl> {
serialize(routerState: RouterStateSnapshot): RouterStateUrl {
let route = routerState.root;
while (route.firstChild) {
route = route.firstChild;
}
const { url, root: { queryParams } } = routerState;
const { params } = route;
// Only return an object including the URL, params and query params
// instead of the entire snapshot
return { url, params, queryParams };
}
}
export const reducers: ActionReducerMap<State> = {
router: routerReducer,
};
@NgModule({
imports: [
StoreModule.forRoot(reducers),
RouterModule.forRoot([
// routes
]),
StoreRouterConnectingModule.forRoot({
stateKey: 'router',
}),
],
providers: [{ provide: RouterStateSerializer, useClass: CustomSerializer }],
})
export class AppModule {}