Custom serialization

The object being stored is a bit verbose though. It contains a lot of information and we may only be interested in parts of it. We can actually solve that by building our own custom serializer. We need to do the following to accomplish that:

  • Create a class that implements the interface RouterStateSerializer and decide what we want to return
  • Replace the router key RouterStateSerializer with our custom implementation

Let's begin. We first create a class, like so:

// my-serializer.ts

import { RouterStateSerializer } from '@ngrx/router-store';
import { RouterStateSnapshot } from '@angular/router';

interface MyState {
url: string;
}

export class MySerializer implements RouterStateSerializer<MyState> {
serialize(routerState: RouterStateSnapshot): MyState {
return <MyState>{};
// todo: implement
}
}

The RouterStateSeralizer interface forces us to specify a type T, which could be anything. T is what we want to return from the routing object. Remember the reason for doing what we are doing is to grab a subset of interesting information from the routing object. The full routing information is contained within our input parameter routerState that is of type RouterStateSnapshot. A comment though is that MyState is a bit anemic, as it only contains a single property, url. You can of course extend this according to the needs of your application. You most likely want to grab the router and query parameters. We will grab those as well before we are done with this section but let's start with this to showcase how it works. The next step is to grab the data from the routerState parameter. For now, we dig out the url—let's update the code to reflect that:

// my-serializer.ts
import { RouterStateSerializer } from '@ngrx/router-store';
import { RouterStateSnapshot } from '@angular/router';

interface MyState {
url: string;
}

export class MySerializer implements RouterStateSerializer<MyState> {
serialize(routerState: RouterStateSnapshot): MyState {
const { url } = routerState;
return
{ url };
}
}

Let's now tell the provider to use our implementation instead. We need to go to the app.module.ts file:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injectable } from '@angular/core';
import { StoreModule, Action } from '@ngrx/store';
import { AppComponent } from './app.component';
import { counterReducer } from './reducer';
import { TodoModule } from './todo/todo.module';
import { todosReducer } from './todo/reducer';
import { JediModule } from './jedi/jedi.module';
import { jediListReducer } from './jedi/list.reducer';
import { productsReducer } from './products/products.reducer';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { ProductsModule } from './products/products.module';
import { StoreRouterConnectingModule, routerReducer, RouterStateSerializer } from '@ngrx/router-store';
import { RouterModule } from '@angular/router';
import { TestingComponent } from './testing.component';
import { Effect, ofType, Actions } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { switchMap } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { EffectsModule } from '@ngrx/effects';
import { RoutingEffects } from './routing.effects';
import { ProductsTestComponent } from './productstest.component';
import { MySerializer } from './my-serializer';

@NgModule({
declarations: [AppComponent, TestingComponent, ProductsTestComponent],
imports: [
BrowserModule,
StoreModule.forRoot({
count: counterReducer,
todos: todosReducer,
jediList: jediListReducer,
products: productsReducer,
router: routerReducer}),
EffectsModule.forRoot([RoutingEffects]),
RouterModule.forRoot([
{ path: 'testing', component: TestingComponent },
{ path: 'products', component: ProductsTestComponent }
]),
StoreRouterConnectingModule.forRoot({
stateKey: 'router' // name of reducer key
}),
StoreDevtoolsModule.instrument({
maxAge: 25 // Retains last 25 states
}),
TodoModule,
JediModule,
ProductsModule
],
providers: [{ provide: RouterStateSerializer, useClass: MySerializer }],
bootstrap: [AppComponent]
})
export class AppModule {}

We have now imported the MySerializer class and the RouterStateSeralizer interface and we are replacing the provider key using the following line:

providers: [{ provide: RouterStateSerializer, useClass: MySerializer }]

Now it's time to take this for a spin. So, we fire up the app and see what happens if we navigate around in the app. Here is a quick reminder of what our app looks like right now:

Clicking either the Testing or Products link will take us to /testing or /products, respectively. Let's do just that and see what that looks like. We have a look at the console and lo and, behold! Our router object is considerably smaller:

Our object now contains pretty much only the url property. This is what gets stored down in the state of our application. If we want more things stored than that, then we can easily extend the MySerializer class—suggested additions are router and query parameters. Let's make the following alterations to the MySerializer class:

// my-serializer.ts
import { RouterStateSerializer } from '@ngrx/router-store';
import { RouterStateSnapshot } from '@angular/router';

interface MyState {
url: string;
queryParams;
}

export class MySerializer implements RouterStateSerializer<MyState> {
serialize(routerState: RouterStateSnapshot): MyState {
const { url, root: { queryParams } } = routerState;
return
{ url, queryParams };
}
}

Navigating to http://localhost:4200/products?page=1 will now produce the following in the console:

The difference is now that we have a queryParams property, which points to an object with content { page: 1 }. This is what we expected. Digging out the router parameters is equally easy. But for us to have router parameters that are populated in the first place, we need to have a route with a routing parameter. Instead of /products, we need something such as products/:id. Let's start by adding that to our list of routes: 

// products/products.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { EffectsModule } from '@ngrx/effects';
import { ProductEffects } from './products.effect';
import { HttpClientModule } from '@angular/common/http';
import { ProductsComponent } from './products.component';
import { FormsModule } from '@angular/forms';
import { ProductsHttpActions } from './products-http.actions';
import { RouterModule } from '@angular/router';
import { ProductsDetailComponent } from './products-detail.component';

@NgModule({
imports: [
BrowserModule,
HttpClientModule,
FormsModule,
EffectsModule.forFeature([ProductEffects]),
RouterModule.forChild([{
path: 'products',
component: ProductsComponent
}, {
path: 'products/:id',
component: ProductsDetailComponent

}])
],
exports: [ProductsComponent],
declarations: [ProductsComponent, ProductsDetailComponent],
providers: [ProductsHttpActions]
})
export class ProductsModule {}

And we of course need to add a component. It does nothing special other than exist for our demo purposes. Remember the emphasis is on understanding the serialization process:

// products-detail.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-products-detail',
template: `
products detail
`
})
export class ProductsDetailComponent{
constructor() {}
}

At this point, it's time to return back to our browser and enter the urlproducts/1?page=1. Let's have a look at the console now:

Here, we see how our params property has been added to our custom object. 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.104.214