Skip to content

Commit 2ab2a08

Browse files
adrianliawdylhunn
authored andcommitted
fix(router): unset attachRef when router-outlet is destroyed to avoid mounting a destroyed component (#43697)
Previously, when a router-outlet is conditionally shown with an ngIf, and a sub-route was re-attached via a custom RouteReuseStrategy, router-outlet would try to mount a destroyed component into the view if the router-outlet is destroyed and re-initialized. This commit fixes it by unsetting context.attachRef when router-outlet is destroyed, so when the router-outlet is being initialized again, it no longer sees an attachRef that it needs to mount to the view. Fixes #43696 PR Close #43697
1 parent 318cf91 commit 2ab2a08

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

packages/router/src/router_outlet_context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export class ChildrenOutletContexts {
5050
const context = this.getContext(childName);
5151
if (context) {
5252
context.outlet = null;
53+
context.attachRef = null;
5354
}
5455
}
5556

packages/router/test/integration.spec.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5941,6 +5941,59 @@ describe('Integration', () => {
59415941
advance(fixture);
59425942
expect(fixture).toContainComponent(Tool2Component, '(e)');
59435943
}));
5944+
5945+
it('should not remount a destroyed component', fakeAsync(() => {
5946+
@Component({
5947+
selector: 'root-cmp',
5948+
template: '<div *ngIf="showRouterOutlet"><router-outlet></router-outlet></div>'
5949+
})
5950+
class RootCmpWithCondOutlet {
5951+
public showRouterOutlet: boolean = true;
5952+
}
5953+
5954+
@NgModule({
5955+
declarations: [RootCmpWithCondOutlet],
5956+
imports: [
5957+
CommonModule,
5958+
RouterTestingModule.withRoutes([
5959+
{path: 'a', component: SimpleCmp},
5960+
{path: 'b', component: BlankCmp},
5961+
]),
5962+
],
5963+
providers: [{provide: RouteReuseStrategy, useClass: AttachDetachReuseStrategy}]
5964+
})
5965+
class TestModule {
5966+
}
5967+
TestBed.configureTestingModule({imports: [TestModule]});
5968+
5969+
const router: Router = TestBed.inject(Router);
5970+
const fixture = createRoot(router, RootCmpWithCondOutlet);
5971+
5972+
// Activate 'a'
5973+
router.navigate(['a']);
5974+
advance(fixture);
5975+
expect(fixture.debugElement.query(By.directive(SimpleCmp))).toBeTruthy();
5976+
5977+
// Deactivate 'a' and detach the route
5978+
router.navigate(['b']);
5979+
advance(fixture);
5980+
expect(fixture.debugElement.query(By.directive(SimpleCmp))).toBeNull();
5981+
5982+
// Activate 'a' again, the route should be re-attached
5983+
router.navigate(['a']);
5984+
advance(fixture);
5985+
expect(fixture.debugElement.query(By.directive(SimpleCmp))).toBeTruthy();
5986+
5987+
// Hide the router-outlet, SimpleCmp should be destroyed
5988+
fixture.componentInstance.showRouterOutlet = false;
5989+
advance(fixture);
5990+
expect(fixture.debugElement.query(By.directive(SimpleCmp))).toBeNull();
5991+
5992+
// Show the router-outlet, SimpleCmp should be re-created
5993+
fixture.componentInstance.showRouterOutlet = true;
5994+
advance(fixture);
5995+
expect(fixture.debugElement.query(By.directive(SimpleCmp))).toBeTruthy();
5996+
}));
59445997
});
59455998
});
59465999

0 commit comments

Comments
 (0)