angular icon indicating copy to clipboard operation
angular copied to clipboard

Router behavior when route reuse strategy is being used with nested routes

Open MateuszBogdan opened this issue 1 year ago • 2 comments

Which @angular/* package(s) are the source of the bug?

router

Is this a regression?

No

Description

Lets say app uses FooComponent and AbcComponent and defines routing as follows:

export const routes: Routes = [
  {
    path: 'Foo', data: {path: '/Foo - root'}, children:
      [
        {path: '', pathMatch: "full", component: FooComponent, data: {path: '/Foo - empty'}},
        {path: 'Bar', data: {path: '/Foo/Bar - root'}, children: [
            {path: '', pathMatch: "full", component: FooComponent, data: {path: '/Foo/Bar - empty'}},
            {path: 'Baz', component: FooComponent, data: {path: '/Foo/Bar/Baz'}},
          ]},
      ],
  },
  {path: 'Abc', component: AbcComponent},
  {path: '**', redirectTo: '/Foo'}
];

Also app is utilising route reuse strategy to reuse component whenever source and destination route both ends up on FooComponent like so:

export class FooRouteReuseStrategy extends BaseRouteReuseStrategy {

  override shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    let futureChild = this.getChildDeep(future);
    let currentChild = this.getChildDeep(curr);


    if (futureChild.component === FooComponent && currentChild.component === FooComponent){
      console.log('shouldReuse: true');
      return true;
    }
    let reuse = super.shouldReuseRoute(future, curr);
    console.log('shouldReuse: ', reuse);
    return reuse;
  }

  private getChildDeep(route: ActivatedRouteSnapshot) {
    while (route.firstChild) {
      route = route.firstChild;
    }
    return route;
  }
}

It seems that in some cases angular router does not properly handle component reuse. I have attached sample repository with minimal replication. Applications logs this specific events: Foo component creation, Foo component destruction, Every call to shouldReuseRoute with returned value.

In some cases router destroys FooComponent despite shouldReuseRoute returns true and in never recreates them back. To replicate this specific error please launch attached application from repository and navigate to http://localhost:4200/Foo.

When you start your application this way and navigate through the /Foo router subtree everything is working as expected, route is reused there is only one Foo Created in console. When you navigate to Abc route Foo is destroyed since it is not supposed to be reused which is expected at this time. After navigating to Abc when you navigate back to Foo/Bar/Baz component is recreated which is expected. Then if you navigate to /Foo the problem manifests. Angular calls RouteReuseStrategy to check if it should reuse route, strategy returns true. Despite that angular destroys FooComponent and never creates new instance of FooComponent.

So the replication goes like that:

  1. Start the app, run browser on http://localhost:4200/Foo
  2. Navigate to Abc route through link
  3. Navigate to either Bar or Bar/Baz links through link
  4. Navigate to Root link by url

Curiously when you skip point 3. app works normally. When you start the app from url http://localhost:4200/Abc and go through the steps 3 - 4 it can be reproduced also.

I don't know if it is connected to this issue or I should issue another ticket, but there seems to be a problem with activatedRoute.data emissions in attached project. FooComponent is displaying activatedRoute.data in its body.

If you start the application from url http://localhost:4200/Foo/Bar/Baz it shows { "path": "/Foo/Bar/Baz" } but if you start the app form url http://localhost:4200/Foo and navigate to http://localhost:4200/Foo/Bar/Baz by link it shows { "path": "/Foo/Bar - root" }. It is inconsistent and seems buggy. I am not sure if it supposed to work that way.

Please provide a link to a minimal reproduction of the bug

https://github.com/MateuszBogdan/angular-router-bug

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 18.2.6
Node: 20.14.0
Package Manager: npm 9.8.1
OS: win32 x64

Angular: 18.2.6
... animations, cli, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1802.6
@angular-devkit/build-angular   18.2.6
@angular-devkit/core            18.2.6
@angular-devkit/schematics      18.2.6
@schematics/angular             18.2.6
rxjs                            7.8.1
typescript                      5.5.4
zone.js                         0.14.10

Anything else?

No response

MateuszBogdan avatar Oct 02 '24 09:10 MateuszBogdan

I have same issue, tried to debug alot but seem nothing wrong in the reuse strategy, maybe some thing wrong with layzyload different nested route

hiepxanh avatar Oct 28 '24 02:10 hiepxanh

You really cannot reuse the ActivatedRoute instance that matched to the route with {path: 'Bar', data: {path: '/Foo/Bar - root'}, children: [...]} for the route {path: '', pathMatch: "full", component: FooComponent, data: {path: '/Foo - empty'}}, which is what the reuse strategy is attempting to do. These aren't even remotely similar and the issue you're seeing is due to the fact that the Bar route doesn't have a component but is trying to be "reused" for the "Foo - empty" route, which does have one and expects it to be rendered.

Ultimately, this is a bug in the Router in that it should probably just not even query shouldReuseRoute if the routeConfig instances differ. Maybe it could work if the components were the same as well but honestly that still leaves the question of all the child routes and how they would compare.

atscott avatar Nov 27 '25 00:11 atscott