From 245b0910eded8a552553098bf9d0ea9dd35d7644 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Fri, 8 Jul 2016 14:47:19 -0700 Subject: [PATCH] feat(router): add activate and deactivate events to RouterOutlet --- .../router/src/directives/router_outlet.ts | 22 +++++++++- modules/@angular/router/test/router.spec.ts | 42 +++++++++++++++++++ tools/public_api_guard/router/index.d.ts | 2 + 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/modules/@angular/router/src/directives/router_outlet.ts b/modules/@angular/router/src/directives/router_outlet.ts index 4d65a7dcab..26729fa677 100644 --- a/modules/@angular/router/src/directives/router_outlet.ts +++ b/modules/@angular/router/src/directives/router_outlet.ts @@ -6,16 +6,17 @@ * found in the LICENSE file at https://angular.io/license */ -import {Attribute, ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, NoComponentFactoryError, ReflectiveInjector, ResolvedReflectiveProvider, ViewContainerRef} from '@angular/core'; +import {Attribute, ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, NoComponentFactoryError, Output, ReflectiveInjector, ResolvedReflectiveProvider, ViewContainerRef} from '@angular/core'; import {RouterOutletMap} from '../router_outlet_map'; import {ActivatedRoute} from '../router_state'; import {PRIMARY_OUTLET} from '../shared'; + /** * A router outlet is a placeholder that Angular dynamically fills based on the application's route. * - * ## Use + * ## Example * * ``` * @@ -23,6 +24,16 @@ import {PRIMARY_OUTLET} from '../shared'; * * ``` * + * A router outlet will emit an activate event any time a new component is being instantiated, + * and a deactivate event when it is being destroyed. + * + * ## Example + * + * ``` + * + * ``` + * * @stable */ @Directive({selector: 'router-outlet'}) @@ -31,6 +42,9 @@ export class RouterOutlet { private _activatedRoute: ActivatedRoute; public outletMap: RouterOutletMap; + @Output('activate') activateEvents = new EventEmitter(); + @Output('deactivate') deactivateEvents = new EventEmitter(); + constructor( parentOutletMap: RouterOutletMap, private location: ViewContainerRef, private resolver: ComponentFactoryResolver, @Attribute('name') name: string) { @@ -49,8 +63,10 @@ export class RouterOutlet { deactivate(): void { if (this.activated) { + const c = this.component; this.activated.destroy(); this.activated = null; + this.deactivateEvents.emit(c); } } @@ -86,5 +102,7 @@ export class RouterOutlet { const inj = ReflectiveInjector.fromResolvedProviders(providers, this.location.parentInjector); this.activated = this.location.createComponent(factory, this.location.length, inj, []); this.activated.changeDetectorRef.detectChanges(); + + this.activateEvents.emit(this.activated.instance); } } diff --git a/modules/@angular/router/test/router.spec.ts b/modules/@angular/router/test/router.spec.ts index f6a43166ed..991030c4ff 100644 --- a/modules/@angular/router/test/router.spec.ts +++ b/modules/@angular/router/test/router.spec.ts @@ -415,6 +415,48 @@ describe('Integration', () => { .toHaveText('primary {simple} right {user victor}'); }))); + it('should emit an event when an outlet gets activated', + fakeAsync(inject( + [Router, TestComponentBuilder, Location], + (router: Router, tcb: TestComponentBuilder, location: Location) => { + @Component({ + selector: 'container', + template: + `` + }) + class Container { + activations: any[] = []; + deactivations: any[] = []; + + recordActivate(component: any): void { this.activations.push(component); } + + recordDeactivate(component: any): void { this.deactivations.push(component); } + } + + const fixture = createRoot(tcb, router, Container); + const cmp = fixture.debugElement.componentInstance; + + router.resetConfig( + [{path: 'blank', component: BlankCmp}, {path: 'simple', component: SimpleCmp}]); + + cmp.activations = []; + cmp.deactivations = []; + + router.navigateByUrl('/blank'); + advance(fixture); + + expect(cmp.activations.length).toEqual(1); + expect(cmp.activations[0] instanceof BlankCmp).toBe(true); + + router.navigateByUrl('/simple'); + advance(fixture); + + expect(cmp.activations.length).toEqual(2); + expect(cmp.activations[1] instanceof SimpleCmp).toBe(true); + expect(cmp.deactivations.length).toEqual(2); + expect(cmp.deactivations[1] instanceof BlankCmp).toBe(true); + }))); + describe('data', () => { class ResolveSix implements Resolve { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): number { return 6; } diff --git a/tools/public_api_guard/router/index.d.ts b/tools/public_api_guard/router/index.d.ts index 349ddde55e..ef970f01c7 100644 --- a/tools/public_api_guard/router/index.d.ts +++ b/tools/public_api_guard/router/index.d.ts @@ -180,8 +180,10 @@ export declare class RouterModule { /** @stable */ export declare class RouterOutlet { + activateEvents: EventEmitter; activatedRoute: ActivatedRoute; component: Object; + deactivateEvents: EventEmitter; isActivated: boolean; outletMap: RouterOutletMap; constructor(parentOutletMap: RouterOutletMap, location: ViewContainerRef, resolver: ComponentFactoryResolver, name: string);