fix(router): make an outlet to unregister itself when it is removed from the dom

This commit is contained in:
vsavkin 2016-08-01 16:56:38 -07:00
parent 8dc82a0080
commit 3e377f520e
4 changed files with 43 additions and 5 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * 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 {RouterOutletMap} from '../router_outlet_map';
import {ActivatedRoute} from '../router_state'; import {ActivatedRoute} from '../router_state';
@ -38,7 +38,7 @@ import {PRIMARY_OUTLET} from '../shared';
* @stable * @stable
*/ */
@Directive({selector: 'router-outlet'}) @Directive({selector: 'router-outlet'})
export class RouterOutlet { export class RouterOutlet implements OnDestroy {
private activated: ComponentRef<any>; private activated: ComponentRef<any>;
private _activatedRoute: ActivatedRoute; private _activatedRoute: ActivatedRoute;
public outletMap: RouterOutletMap; public outletMap: RouterOutletMap;
@ -47,11 +47,13 @@ export class RouterOutlet {
@Output('deactivate') deactivateEvents = new EventEmitter<any>(); @Output('deactivate') deactivateEvents = new EventEmitter<any>();
constructor( constructor(
parentOutletMap: RouterOutletMap, private location: ViewContainerRef, private parentOutletMap: RouterOutletMap, private location: ViewContainerRef,
private resolver: ComponentFactoryResolver, @Attribute('name') name: string) { private resolver: ComponentFactoryResolver, @Attribute('name') private name: string) {
parentOutletMap.registerOutlet(name ? name : PRIMARY_OUTLET, this); 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 isActivated(): boolean { return !!this.activated; }
get component(): Object { get component(): Object {
if (!this.activated) throw new Error('Outlet is not activated'); if (!this.activated) throw new Error('Outlet is not activated');

View File

@ -15,4 +15,6 @@ export class RouterOutletMap {
/** @internal */ /** @internal */
_outlets: {[name: string]: RouterOutlet} = {}; _outlets: {[name: string]: RouterOutlet} = {};
registerOutlet(name: string, outlet: RouterOutlet): void { this._outlets[name] = outlet; } registerOutlet(name: string, outlet: RouterOutlet): void { this._outlets[name] = outlet; }
removeOutlet(name: string): void { this._outlets[name] = undefined; }
} }

View File

@ -63,6 +63,38 @@ describe('Integration', () => {
expect(location.path()).toEqual('/child/simple'); 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: `<div *ngIf="cond"><router-outlet></router-outlet></div>`,
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', it('should update location when navigating',
fakeAsync(inject( fakeAsync(inject(
[Router, TestComponentBuilder, Location], [Router, TestComponentBuilder, Location],

View File

@ -228,7 +228,7 @@ export declare class RouterModule {
} }
/** @stable */ /** @stable */
export declare class RouterOutlet { export declare class RouterOutlet implements OnDestroy {
activateEvents: EventEmitter<any>; activateEvents: EventEmitter<any>;
activatedRoute: ActivatedRoute; activatedRoute: ActivatedRoute;
component: Object; component: Object;
@ -238,11 +238,13 @@ export declare class RouterOutlet {
constructor(parentOutletMap: RouterOutletMap, location: ViewContainerRef, resolver: ComponentFactoryResolver, name: string); constructor(parentOutletMap: RouterOutletMap, location: ViewContainerRef, resolver: ComponentFactoryResolver, name: string);
activate(activatedRoute: ActivatedRoute, loadedResolver: ComponentFactoryResolver, loadedInjector: Injector, providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void; activate(activatedRoute: ActivatedRoute, loadedResolver: ComponentFactoryResolver, loadedInjector: Injector, providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void;
deactivate(): void; deactivate(): void;
ngOnDestroy(): void;
} }
/** @stable */ /** @stable */
export declare class RouterOutletMap { export declare class RouterOutletMap {
registerOutlet(name: string, outlet: RouterOutlet): void; registerOutlet(name: string, outlet: RouterOutlet): void;
removeOutlet(name: string): void;
} }
/** @stable */ /** @stable */