4

I have an Angular component that needs to respond to route parameter changes that apply to the current page. For example, maybe I have the route page/:id, and if the :id changes, I want to respond by reloading the component's content.

Using the ActivatedRoute's paramMap, this is easy. While I'm on the page/:id route, I can change the :id in the URL and the application responds appropriately. When I navigate away from the page/:id route, the component is destroyed, and no parameter changes are emitted.

However, if I try the same approach with @ngrx/router-store, more route param changes are emitted than I actually want. I am getting the route params from the route that is being navigated to. This seems to happen because the router-store emits before my component gets destroyed.

I need a way to say "I only want the route params for this page, and as soon as this page is navigated away from, I need you to stop responding to route param changes." I thought this would be as easy as implementing a takeUntil(unsubscriber$) which completes in my ngOnDestroy(). However, that doesn't work because the next params emission happens before the ngOnDestroy() gets called.

I imagine this behavior is by design, but I'm not sure how to overcome the problem I'm facing.

I've worked up a Stackblitz that demonstrates the issue.

What's the most elegant way to handle this situation using @ngrx/router-store?

Working example using ActivatedRoute

export class MyComponent implements OnInit, OnDestroy {
  unsubscriber$ = new Subject<void>();

  constructor(public route: ActivatedRoute) {}

  ngOnInit() {
    this.route.paramMap
      .pipe(takeUntil(this.unsubscriber$))
      .subscribe(params => {
        // reload data
      });
  }

  ngOnDestroy() {
    this.unsubscriber$.next();
    this.unsubscriber$.complete();
  }
}

Non-working example using router-store

export class MyComponent implements OnInit, OnDestroy {
  unsubscriber$ = new Subject<void>();

  constructor(public store: Store<State>) {}

  ngOnInit() {
    this.store
      .pipe(select(selectRouteParams), takeUntil(this.unsubscriber$))
      .subscribe(params => {
        // reload data
        // this code is being run even when this component is being navigated away from
      });
  }

  ngOnDestroy() {
    this.unsubscriber$.next();
    this.unsubscriber$.complete();
  }
}

1 Answers1

4

Change your navigation action timing to PostActivation via the configuration object passed in to StoreRouterConnectingModule.forRoot:

@NgModule({
  imports: [
    CommonModule,
    ...
    StoreRouterConnectingModule.forRoot({
      navigationActionTiming: NavigationActionTiming.PostActivation,
    }),
  ],
})
Christian Jensen
  • 900
  • 1
  • 10
  • 25
  • I'm confused as to why this isn't the default. Without it set you get param changes before the url has even changed, meaning the state doesn't actually represent the current route state. – james May 17 '22 at 12:39