-
-
Notifications
You must be signed in to change notification settings - Fork 12
4. Handling router state with @ngrx router store
In the exercise we want to store the search params in the url and find our flights based on the url params. We also will sync the url with our search form.
Before we will use the ngrx/router-store we try to implement the a custom way of making the url the source of truth.
Preconditions:
- Install the
@ngrx/router-store
over npm and persist it to the package.json file.
npm install --save @ngrx/router-store
- Custom implementation of url state
1.1 Subscribe to url changes
// app/pages/flight/components/search/search.component.ts
...
// add import
import {ActivatedRoute} from '@angular/router';
...
constructor(
...
// inject activated route
private route: ActivatedRoute
) {
...
// add subscription
this.route.params.subscribe(
(data: { from: string, to: string }) => {
// find flights triggered by on ulr param change
this.fs.find(data.from, data.to)
}
)
1.2 Trigger url change instead of flight search
...
import { ActivatedRoute,
// add import
Router } from '@angular/router';
// search.component.ts
constructor(
...
// inject router
private router: Router
) {
...
}
searchFlights(form: FormGroup) {
const data = form.value
// this.fs.find(data.from, data.to)
// trigger navigation
this.router.navigate(['./', {from: data.from, to: data.to}])
}
...
refreshFlights() {
// this.fs.find(null, null)
this.router.navigate(['./', {from: null, to: null}])
}
1.3 Sync the form search params with the url state
// app/pages/flight/components/search.component.ts
...
constructor(
private route: ActivatedRoute,
private router: Router,
private fb: FormBuilder,
private fs: FlightService
) {
...
this.route.params.subscribe(
(data: { from: string, to: string }) => {
// ensure that from and to are always provided
const searchParams = Object.assign({from: '', to: ''}, data)
this.searchForm.patchValue(searchParams)
...
}
)
...
}
- ngrx/router-store implementation to sync with url params Now we will use the router-store to sync with url changes
2.1. connect router-store to routing actions
// app/app.module.ts
...
import * as fromRouter from '@ngrx/router-store';
export interface IDB {
...
// extend state
routerBranch: fromRouter.RouterReducerState<any>
}
const reducer = {
...,
// implement reducer
routerBranch: fromRouter.routerReducer
}
@NgModule({
...
imports: [
...,
// connect router-store
fromRouter.StoreRouterConnectingModule
],
...
})
export class AppModule {
}
2.2. Listen to router actions and trigger find flights action
The router-store module, more accurate the router,
will dispatch a ROUTER_NAVIGATION
action.
We can listen to it in any ´@Effect´.
// app/ngrx/flight.effects.ts
...
@Injectable()
export class FlightEffects {
find$: ...
...
@Effect()
// handle location update
locationUpdate$: Observable<Action> = this.actions$.ofType('ROUTER_NAVIGATION')
.filter((n: any) => {
return n.payload.event.url.indexOf('flight')
})
.switchMap((action: any) => {
// extract params from url
const rS = action.payload.routerState
const searchParams = rS.root.firstChild.params
// trigger FindAction with search params
return Observable.of(new flight.FindAction(searchParams))
});
...
}
2.3. Remove manual calls to find flights
// app/pages/flight/components/search/search.component.ts
...
constructor(...) {
...
this.route.params.subscribe(
(data: { from: string, to: string }) => {
const searchFormData = Object.assign({from: '', to: ''}, data)
this.searchForm.patchValue(searchFormData)
// remove find flights call
// this.fs.find(data.from, data.to)
}
)
...
}
...
Now you implemented ngrx-router-store to keep track of url state. Test your app!
- Create the file
router-state.serializer.ts
and copy this code into it:
//app/ngrx/router-state.serializer.ts
import {RouterStateSerializer} from '@ngrx/router-store';
import {Params, RouterStateSnapshot} from '@angular/router';
export interface IRouterStateUrl {
url: string;
params: any;
}
export class CustomSerializer implements RouterStateSerializer<IRouterStateUrl> {
serialize(routerState: RouterStateSnapshot): IRouterStateUrl {
const { url } = routerState;
const params = routerState.root.firstChild.params;
console.log('routerState', routerState)
// Only return an object including the URL and query params
// instead of the entire snapshot
return { url, params };
}
}
- Adopt
IDB
interface with newIRouterStateUrl
// app/app.module.ts
...
import {CustomSerializer, IRouterStateUrl} from './ngrx/router-state.serializer';
export interface IDB {
flightPage: IFlightState
//routerReducer: fromRouter.RouterReducerState<any>
routerReducer: fromRouter.RouterReducerState<IRouterStateUrl>
}
...
- Override provider for
RouterStateSerializer
with theCustomSerializer
// app/app.module.ts
...
import {CustomSerializer,
// add import
IRouterStateUrl} from './ngrx/router-state.serializer';
...
@NgModule({
...
providers: [
{provide: RouterStateSerializer, useClass: CustomSerializer}
],
...
})
export class AppModule {
}
- Adopt
locationUpdate$
for new RouterState
@Effect()
// handle location update
locationUpdate$: Observable<Action> = this.actions$.ofType('ROUTER_NAVIGATION')
...
.switchMap((action: any) => {
// const rS = action.payload.routerState
// const searchParams = rS.root.firstChild.params
const searchParams = action.payload.routerState.params
return Observable.of(new flight.FindAction(searchParams))
});
Congrats! You implemented a custom routerState serializer. Test it!