NgRx: selector gets called on the transition to a different page

768 Views Asked by At

I'm new to NgRx. After a bunch of reading, I added the router (@ngrx/router-store) to the app state to access the router parameter in the selector. I'm using a CustomRouterSerializer together with RouterState.Minimal. Since then I'm facing a problem which is when I'm navigating away from the detail page (/hero/:heroId) to list page (/) for example, the getSelectedHero selector from the detail page gets called which leads to other problems. Sure I can put null checks in...

But since I'm a beginner, I thought I'd better ask. It seems to be wrong that the getSelectedHero gets called on the transition to a different page. Sure the router state is changing, but?

I spent some time trying to find a solution that doesn't include an undefined check.

I created a "super" application that demonstrates the issue. I pushed it up to GitHub and to StackBlitz.

Here is the selector code from the detail page (/hero/:heroId) which gets called when navigating away from it.

export const getSelectedHero = createSelector(
  selectHeroesState,
  getRouteState,
  (state, router) => {
    const hero = state.entities[router.state.params.heroId];
    // FixMe: simplifies the problem... hero is undefined if you navigation for /hero/:heroId to / and following line will throw an error.
    console.log("getSelectedHero", hero, hero.id, hero.name); 
    return hero;
  }
);

In the detail page (/hero/:heroId) I use the following:    

public ngOnInit() {
  this.hero$ = this.store.pipe(select(getSelectedHero));
}
1

There are 1 best solutions below

4
The Fabio On

The problem is that when you navigate to other routes the route parameter :heroId is not available.

when you navigate to a hero page, .../hero/2 for example, the route param :heroId is available, because you have defined the route hero/:heroId;

When you navigate to the root page .../ the route matches "", and that causes the route param :heroId not to be set (it appears as undefined on your selector)

const hero = state.entities[router.state.params.heroId]; // <-- here "router.state.params.heroId" is undefined

Because your getSelectedHero uses selector the route state selector getRouteState, every time there is a route change and the subscription under hero$ is active, the getSelectedHero will be called.

to resolve you could do:

if (router.state.params.heroId === undefined) return {};

Or you could move the selected heroId variable to the hero state, this way the navigation does not trigger the hero data selection