Skip to content

Commit

Permalink
fix(router): remove child outlet context from parent on deactivation
Browse files Browse the repository at this point in the history
When we deactivate a child route, we deactivate its outlet as well as
its children. We also need to remove this context from the parent `Map`.
If we do not, the parent will hold on to a reference to this deactivated
context and can result in reactivating an outlet that was deactivated by
the previous navigation.

Fixes #41379
  • Loading branch information
atscott committed Mar 30, 2021
1 parent 5e46901 commit a078698
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 1 deletion.
1 change: 1 addition & 0 deletions goldens/public-api/router/router.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export declare class ChildrenOutletContexts {
getContext(childName: string): OutletContext | null;
getOrCreateContext(childName: string): OutletContext;
onChildOutletCreated(childName: string, outlet: RouterOutletContract): void;
onChildOutletDeactivated(childName: string): void;
onChildOutletDestroyed(childName: string): void;
onOutletDeactivated(): Map<string, OutletContext>;
onOutletReAttached(contexts: Map<string, OutletContext>): void;
Expand Down
1 change: 1 addition & 0 deletions packages/router/src/operators/activate_routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export class ActivateRoutes {
context.outlet.deactivate();
// Destroy the contexts for all the outlets that were in the component
context.children.onOutletDeactivated();
parentContexts.onChildOutletDeactivated(route.value.outlet);
}
}

Expand Down
7 changes: 7 additions & 0 deletions packages/router/src/router_outlet_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ export class ChildrenOutletContexts {
}
}

/**
* Called when the corresponding child route is deactivated during navigation.
*/
onChildOutletDeactivated(childName: string): void {
this.contexts.delete(childName);
}

/**
* Called when the corresponding route is deactivated during navigation.
* Because the component get destroyed, all children outlet are destroyed.
Expand Down
42 changes: 41 additions & 1 deletion packages/router/test/regression_integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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: ` <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 {
Expand Down

0 comments on commit a078698

Please sign in to comment.