fix(router): prevent componentless routes from being detached (#44240)

Currently, when deactivating a route, the detach method of the RouteReuseStrategy is always called
even when we are dealing with a componentless route. If when using a custom strategy we attempt to
detach a componentless route, an exception is thrown causing the navigation to break.
This change prevents this from happening, by not triggering the detach of the route reuse strategy.

Fixes #44239

PR Close #44240
This commit is contained in:
unknown
2021-11-20 23:13:53 +00:00
committed by Jessica Janiuk
parent 1beda660f1
commit d265d0d241
2 changed files with 54 additions and 1 deletions
@@ -89,7 +89,9 @@ export class ActivateRoutes {
private deactivateRouteAndItsChildren(
route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): void {
if (this.routeReuseStrategy.shouldDetach(route.value.snapshot)) {
// If there is no component, the Route is never attached to an outlet (because there is no
// component to attach).
if (route.value.component && this.routeReuseStrategy.shouldDetach(route.value.snapshot)) {
this.detachAndStoreRouteSubtree(route, parentContexts);
} else {
this.deactivateRouteAndOutlet(route, parentContexts);
+51
View File
@@ -6083,6 +6083,57 @@ describe('Integration', () => {
advance(fixture);
expect(createdComps).toEqual(['parent', 'child', 'child']);
}));
it('should not try to detach the outlet of a route that does not get to attach a component',
fakeAsync(() => {
@Component({selector: 'root', template: `<router-outlet></router-outlet>`})
class Root {
}
@Component({selector: 'component-a', template: 'Component A'})
class ComponentA {
}
@Component({selector: 'component-b', template: 'Component B'})
class ComponentB {
}
@NgModule({
declarations: [ComponentA],
imports: [RouterModule.forChild([{path: '', component: ComponentA}])],
})
class LoadedModule {
}
@NgModule({
declarations: [Root, ComponentB],
imports: [RouterTestingModule.withRoutes([
{path: 'a', loadChildren: () => LoadedModule}, {path: 'b', component: ComponentB}
])],
providers: [
{provide: RouteReuseStrategy, useClass: AttachDetachReuseStrategy},
]
})
class TestModule {
}
TestBed.configureTestingModule({imports: [TestModule]});
const router = TestBed.inject(Router);
const strategy = TestBed.inject(RouteReuseStrategy);
const fixture = createRoot(router, Root);
spyOn(strategy, 'shouldDetach').and.callThrough();
router.navigateByUrl('/a');
advance(fixture);
// Deactivate 'a'
// 'shouldDetach' should not be called for the componentless route
router.navigateByUrl('/b');
advance(fixture);
expect(strategy.shouldDetach).toHaveBeenCalledTimes(1);
}));
});
});