diff --git a/modules/@angular/router/src/directives/router_outlet.ts b/modules/@angular/router/src/directives/router_outlet.ts index 2d32b4ea96..c0cd11296a 100644 --- a/modules/@angular/router/src/directives/router_outlet.ts +++ b/modules/@angular/router/src/directives/router_outlet.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Attribute, ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, NoComponentFactoryError, Output, ReflectiveInjector, ResolvedReflectiveProvider, ViewContainerRef} from '@angular/core'; +import {Attribute, ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, NoComponentFactoryError, OnDestroy, Output, ReflectiveInjector, ResolvedReflectiveProvider, ViewContainerRef} from '@angular/core'; import {RouterOutletMap} from '../router_outlet_map'; import {ActivatedRoute} from '../router_state'; @@ -38,7 +38,7 @@ import {PRIMARY_OUTLET} from '../shared'; * @stable */ @Directive({selector: 'router-outlet'}) -export class RouterOutlet { +export class RouterOutlet implements OnDestroy { private activated: ComponentRef; private _activatedRoute: ActivatedRoute; public outletMap: RouterOutletMap; @@ -47,11 +47,13 @@ export class RouterOutlet { @Output('deactivate') deactivateEvents = new EventEmitter(); constructor( - parentOutletMap: RouterOutletMap, private location: ViewContainerRef, - private resolver: ComponentFactoryResolver, @Attribute('name') name: string) { + private parentOutletMap: RouterOutletMap, private location: ViewContainerRef, + private resolver: ComponentFactoryResolver, @Attribute('name') private name: string) { parentOutletMap.registerOutlet(name ? name : PRIMARY_OUTLET, this); } + ngOnDestroy(): void { this.parentOutletMap.removeOutlet(this.name ? this.name : PRIMARY_OUTLET); } + get isActivated(): boolean { return !!this.activated; } get component(): Object { if (!this.activated) throw new Error('Outlet is not activated'); diff --git a/modules/@angular/router/src/router_outlet_map.ts b/modules/@angular/router/src/router_outlet_map.ts index e81ef07172..5c9bef1dee 100644 --- a/modules/@angular/router/src/router_outlet_map.ts +++ b/modules/@angular/router/src/router_outlet_map.ts @@ -15,4 +15,6 @@ export class RouterOutletMap { /** @internal */ _outlets: {[name: string]: RouterOutlet} = {}; registerOutlet(name: string, outlet: RouterOutlet): void { this._outlets[name] = outlet; } + + removeOutlet(name: string): void { this._outlets[name] = undefined; } } diff --git a/modules/@angular/router/test/router.spec.ts b/modules/@angular/router/test/router.spec.ts index 7de6bd870a..2f5b1c774e 100644 --- a/modules/@angular/router/test/router.spec.ts +++ b/modules/@angular/router/test/router.spec.ts @@ -63,6 +63,38 @@ describe('Integration', () => { expect(location.path()).toEqual('/child/simple'); }))); + it('should work when an outlet is in an ngIf (and is removed)', + fakeAsync(inject( + [Router, TestComponentBuilder, Location], + (router: Router, tcb: TestComponentBuilder, location: Location) => { + @Component({ + selector: 'someRoot', + template: `
`, + entryComponents: [BlankCmp, SimpleCmp] + }) + class RootCmpWithLink { + cond: boolean = true; + } + + const fixture = createRoot(tcb, router, RootCmpWithLink); + + router.resetConfig( + [{path: 'simple', component: SimpleCmp}, {path: 'blank', component: BlankCmp}]); + + router.navigateByUrl('/simple'); + advance(fixture); + expect(location.path()).toEqual('/simple'); + + const instance = fixture.componentInstance; + instance.cond = false; + advance(fixture); + + let recordedError: any = null; + router.navigateByUrl('/blank').catch(e => recordedError = e); + advance(fixture); + expect(recordedError.message).toEqual('Cannot find primary outlet to load \'BlankCmp\''); + }))); + it('should update location when navigating', fakeAsync(inject( [Router, TestComponentBuilder, Location], diff --git a/tools/public_api_guard/router/index.d.ts b/tools/public_api_guard/router/index.d.ts index 89ac50745a..e96aafc0f9 100644 --- a/tools/public_api_guard/router/index.d.ts +++ b/tools/public_api_guard/router/index.d.ts @@ -228,7 +228,7 @@ export declare class RouterModule { } /** @stable */ -export declare class RouterOutlet { +export declare class RouterOutlet implements OnDestroy { activateEvents: EventEmitter; activatedRoute: ActivatedRoute; component: Object; @@ -238,11 +238,13 @@ export declare class RouterOutlet { constructor(parentOutletMap: RouterOutletMap, location: ViewContainerRef, resolver: ComponentFactoryResolver, name: string); activate(activatedRoute: ActivatedRoute, loadedResolver: ComponentFactoryResolver, loadedInjector: Injector, providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void; deactivate(): void; + ngOnDestroy(): void; } /** @stable */ export declare class RouterOutletMap { registerOutlet(name: string, outlet: RouterOutlet): void; + removeOutlet(name: string): void; } /** @stable */