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
This commit is contained in:
Andrew Scott 2021-03-30 09:30:27 -07:00 committed by Zach Arend
parent 0a4308f756
commit d28a391385
2 changed files with 46 additions and 1 deletions

View File

@ -123,6 +123,11 @@ export class ActivateRoutes {
context.outlet.deactivate(); context.outlet.deactivate();
// Destroy the contexts for all the outlets that were in the component // Destroy the contexts for all the outlets that were in the component
context.children.onOutletDeactivated(); 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;
} }
} }

View File

@ -7,7 +7,7 @@
*/ */
import {CommonModule} from '@angular/common'; 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 {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {RouterTestingModule} from '@angular/router/testing'; import {RouterTestingModule} from '@angular/router/testing';
@ -187,6 +187,46 @@ describe('Integration', () => {
expect(fixture.nativeElement.innerHTML).toContain('isActive: true'); 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: ` <router-outlet *ngIf="outletVisible" name="aux"></router-outlet> `})
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>(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<T>(fixture: ComponentFixture<T>): void { function advance<T>(fixture: ComponentFixture<T>): void {