rweisse
rweisse

Reputation: 830

NgRx: CustomRouterStateSerializer serialize() called multiple times

I have implemented a NgRx Router-Store for my Angular project in the common way (https://ngrx.io/guide/router-store/configuration).

My "Problem" is that the serialize-method of my CustomRouterStateSerializer seems to be called multiple times, when a routerLink gets triggered by clicking the corresponding html element of a component.

You'll find a minimal example application of the following description on StackBlitz.


My implementation

This is my router.reducer.ts file which contains the RouterStateUrl-Interface and the Serializer-Class:

export interface RouterStateUrl  {
    url: string;
    queryParams: Params;
    params: Params;
    random: number;
}

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

        // Random number to be able to match console output to router-state later (with NgRx Store DevTools)
        const random = Math.random();
        console.warn(`CustomRouterStateSerializer called by ${url}, random: ${random}`);

        let state: ActivatedRouteSnapshot = routerState.root;
        while(state.firstChild){
            state = state.firstChild;
        }
        const { params } = state;
        return {url, queryParams, params, random };
    }
}

This is my app.module.ts file:

/*[...]*/
imports: [
    /*[...],*/
    StoreModule.forRoot(reducers, {
        metaReducers,
        runtimeChecks: {
            strictStateImmutability: true,
            strictActionImmutability: true
        }
    }),
    StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: environment.development }),
    EffectsModule.forRoot([AppEffects]),
    StoreRouterConnectingModule.forRoot({
        serializer: CustomRouterStateSerializer,
        navigationActionTiming: NavigationActionTiming.PostActivation,
    }),
]
/*[...]*/


Detailed description and output

Lets say my app currently displays an overview of some projects (url: /projects) and a routerLink gets triggert to switch the component to show an overview of jobs (url: /jobs). The console will print three messages:

NgRx Store DevTools are showing several Actions as expected:

@ngrx/router-store/request

router: {
    state: {
        url: '/projects',
        queryParams: {},
        params: {},
        random: 0.31957045879116797
    },
    navigationId: 2
}

@ngrx/router-store/navigation

router: {
    state: {
        url: '/jobs',
        queryParams: {},
        params: {},
        random: 0.7662025752972623
    },
    navigationId: 3
}

@ngrx/router-store/navigated

router: {
    state: {
      url: '/jobs',
      queryParams: {},
      params: {},
      random: 0.7662025752972623
    },
    navigationId: 3
}

As you can see the state of @ngrx/router-store/navigation and @ngrx/router-store/navigated are identical. Furthermore their random number is identical to the second console output. The random number of @ngrx/router-store/request belongs to the state of the old projects-view.

The output of NgRx Store DevTools seem to be as expected. But I don't understand when and what called the serialize method where the other console outputs were triggert. I can not find any random numbers of the first and the third console output in any state. Now I'm asking myself if I have made an error (implementing stuff) or if this is just normal behaviour (but why?). Maybe some of you can tell me.

Upvotes: 1

Views: 1685

Answers (1)

Ferdinand Malcher
Ferdinand Malcher

Reputation: 211

I tried out a few things, but in the end it helped to take a look at the source code of @ngrx/router-store. What this module essentially does is to listen to all router events and dispatch actions. The important piece of code is this one: https://github.com/ngrx/platform/blob/master/modules/router-store/src/router_store_module.ts#L240-L275

But I don't understand when and what called the serialize method where the other console outputs were triggert.

You can see here that there are three situations where serialize() can be called:

  • at NavigationStart (directly)
  • at NavigationEnd (through the dispatchRouterNavigation() and dispatchRouterNavigated() methods)
  • at RoutesRecognized (through dispatchRouterNavigated())

Each of these events call serialize() separately to avoid derived state: The router is the source of the router state and it can change at any given time. Thus, when the serialized state is needed, it will not be stored somewhere but newly calculated each time instead. This is why the function is being called multiple times. However, since the serializer function should be pure, this is not a problem at all but rather part of the design.

I can not find any random numbers of the first and the third console output in any state.

The reducer will only put the router state into the store on navigation, cancel and error – but not on request and navigated. That means, the only random number you will see in the store is the one originating from the navigation action. All others are just used "on the way".

Now I'm asking myself if I have made an error (implementing stuff) or if this is just normal behaviour (but why?).

Your implementation looks good and you can be relieved: This is normal and intended behavior! 🙂 Just be aware that your serializer function should be pure.

Hope this helps!

Upvotes: 3

Related Questions