Search code examples
angularangular-ui-router

angular 11.2.9 routerLink doesn't re-initialise same component


I am doing something wrong but cannot find exact solution for this issue.

I have a router with a view component A:

const routes: Routes = [
  { path: '', redirectTo: '/list', pathMatch: 'full' },
  { path: 'list', component: ListComponent },
  { path: 'a/:id/:version', component: AComponent, resolve: { a: DataResolver}, runGuardsAndResolvers: 'always' }
];

Everything is working fine - I am navigating to component A like /a/1/v1 - everything is initialized perfectly.

THE PROBLEM: there is a functionality that component is loading other versions of the view's content and I would expect that if I just click <a routerLink="/a/1/v2">load version 2</a> somewhere from within A component's view, the view is regenerated.

actually what happens is

  • url in browser is changed
  • data provider associated with route is called and new data associated with new url is fetched
  • constructor or ngOnInit is not called

the funny thing is that if I configure /b/.. and assign it to the same A component in router's configuration, everything works as expected after navigating from /a/1/v1 to /b/1/v2.

const routes: Routes = [
  { path: '', redirectTo: '/list', pathMatch: 'full' },
  { path: 'list', component: ListComponent },
  { path: 'a/:id/:version', component: AComponent, resolve: { a: DataResolver}, runGuardsAndResolvers: 'always' },
  { path: 'b/:id/:version', component: AComponent, resolve: { a: DataResolver}, runGuardsAndResolvers: 'always' },
];

I am a bit lost. Thanks for your help.


Solution

  • The reason the constructor and ngOnInit is not called upon navigating to your component stems from Angular's default RouteReuseStrategy.

    This base route reuse strategy only reuses routes when the matched router configs are identical. This prevents components from being destroyed and recreated when just the fragment or query parameters change (that is, the existing component is reused).

    In other words, because only the fragment of your URL changed, /a/1/v1 -> /a/1/v2, Angular tries to be smart and reuse the component. This prevents the constructor and ngOnInit from being called again. (I know, infuriating, right?)

    There are a couple of solutions I can think of.

    1. Listen for when the route params change and handle any initialization logic in the callback. This means you would need to subscribe to the route params observable and move the logic that was originally in the ngOnInit into the subscribe.
    class AComponent implements OnInit {
        constructor(private _route: ActivatedRoute) {}
        
        ngOnInit(): void {
            this._route.params.subscribe(newParams => {
                 // handle any initialization logic here.
            }); 
        }
    
    }
    
    1. You can override the Angular default RouteReuseStrategy with your own to prevent Angular from ever reusing a component for a route. This is the approach we ended up taking. It's easy to forget about this Angular "feature". You can override the default route resuse strategy like so.
    export class MyCustomRouteReuseStrategy implements RouteReuseStrategy {
      // Never reuse a component!
      shouldReuseRoute(): boolean {
        return false;
      }
    
      // Implement the other default methods. Keep same functionality.
      shouldDetach(): boolean { return false; }
      store(): void {}
      shouldAttach(): boolean { return false; }
      retrieve(): DetachedRouteHandle | null { return null; }
    }
    
    // app.module.ts
    @NgModule({
      providers: [
        { provide: RouteReuseStrategy, useClass: MyCustomRouteReuseStrategy },
      ]