From d28a391385ca14225bf63ecf46d80181211c6919 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 30 Mar 2021 09:30:27 -0700 Subject: [PATCH] fix(router): Remove information about attached component when deactivating route (#41381) When we deactivate a child route, we deactivate its outlet as well as its children. We also need to clear the stored information about the route and the associated component. If we do not, the context will keep these references and can result in reactivating an outlet that was deactivated by the previous navigation. Fixes #41379 PR Close #41381 --- .../router/src/operators/activate_routes.ts | 5 +++ .../test/regression_integration.spec.ts | 42 ++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/router/src/operators/activate_routes.ts b/packages/router/src/operators/activate_routes.ts index 094da9649c..8abe7300f1 100644 --- a/packages/router/src/operators/activate_routes.ts +++ b/packages/router/src/operators/activate_routes.ts @@ -123,6 +123,11 @@ export class ActivateRoutes { context.outlet.deactivate(); // Destroy the contexts for all the outlets that were in the component context.children.onOutletDeactivated(); + // Clear the information about the attached component on the context but keep the reference to + // the outlet. + context.attachRef = null; + context.resolver = null; + context.route = null; } } diff --git a/packages/router/test/regression_integration.spec.ts b/packages/router/test/regression_integration.spec.ts index 9dd5b8ba32..84b5117c5e 100644 --- a/packages/router/test/regression_integration.spec.ts +++ b/packages/router/test/regression_integration.spec.ts @@ -7,7 +7,7 @@ */ import {CommonModule} from '@angular/common'; -import {ChangeDetectionStrategy, Component, ContentChild, NgModule, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, NgModule, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core'; import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; import {Router} from '@angular/router'; import {RouterTestingModule} from '@angular/router/testing'; @@ -187,6 +187,46 @@ describe('Integration', () => { expect(fixture.nativeElement.innerHTML).toContain('isActive: true'); })); }); + + it('should not reactivate a deactivated outlet when destroyed and recreated - #41379', + fakeAsync(() => { + @Component({template: 'simple'}) + class SimpleComponent { + } + + @Component({template: ` `}) + class AppComponent { + outletVisible = true; + } + + TestBed.configureTestingModule({ + imports: [RouterTestingModule.withRoutes( + [{path: ':id', component: SimpleComponent, outlet: 'aux'}])], + declarations: [SimpleComponent, AppComponent], + }); + + const router = TestBed.inject(Router); + const fixture = createRoot(router, AppComponent); + const componentCdr = fixture.componentRef.injector.get(ChangeDetectorRef); + + router.navigate([{outlets: {aux: ['1234']}}]); + advance(fixture); + expect(fixture.nativeElement.innerHTML).toContain('simple'); + + router.navigate([{outlets: {aux: null}}]); + advance(fixture); + expect(fixture.nativeElement.innerHTML).not.toContain('simple'); + + fixture.componentInstance.outletVisible = false; + componentCdr.detectChanges(); + expect(fixture.nativeElement.innerHTML).not.toContain('simple'); + expect(fixture.nativeElement.innerHTML).not.toContain('router-outlet'); + + fixture.componentInstance.outletVisible = true; + componentCdr.detectChanges(); + expect(fixture.nativeElement.innerHTML).toContain('router-outlet'); + expect(fixture.nativeElement.innerHTML).not.toContain('simple'); + })); }); function advance(fixture: ComponentFixture): void {