/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, OnInit, Output, ViewContainerRef} from '@angular/core'; import {ChildrenOutletContexts} from '../router_outlet_context'; import {ActivatedRoute} from '../router_state'; import {PRIMARY_OUTLET} from '../shared'; /** * @whatItDoes Acts as a placeholder that Angular dynamically fills based on the current router * state. * * @howToUse * * ``` * * * * ``` * * 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. * * ``` * * ``` * @ngModule RouterModule * * @stable */ @Directive({selector: 'router-outlet', exportAs: 'outlet'}) export class RouterOutlet implements OnDestroy, OnInit { private activated: ComponentRef|null = null; private _activatedRoute: ActivatedRoute|null = null; private name: string; @Output('activate') activateEvents = new EventEmitter(); @Output('deactivate') deactivateEvents = new EventEmitter(); constructor( private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef, private resolver: ComponentFactoryResolver, @Attribute('name') name: string, private changeDetector: ChangeDetectorRef) { this.name = name || PRIMARY_OUTLET; parentContexts.onChildOutletCreated(this.name, this); } ngOnDestroy(): void { this.parentContexts.onChildOutletDestroyed(this.name); } ngOnInit(): void { if (!this.activated) { // If the outlet was not instantiated at the time the route got activated we need to populate // the outlet when it is initialized (ie inside a NgIf) const context = this.parentContexts.getContext(this.name); if (context && context.route) { if (context.attachRef) { // `attachRef` is populated when there is an existing component to mount this.attach(context.attachRef, context.route); } else { // otherwise the component defined in the configuration is created this.activateWith(context.route, context.resolver || null); } } } } /** @deprecated since v4 **/ get locationInjector(): Injector { return this.location.injector; } /** @deprecated since v4 **/ get locationFactoryResolver(): ComponentFactoryResolver { return this.resolver; } get isActivated(): boolean { return !!this.activated; } get component(): Object { if (!this.activated) throw new Error('Outlet is not activated'); return this.activated.instance; } get activatedRoute(): ActivatedRoute { if (!this.activated) throw new Error('Outlet is not activated'); return this._activatedRoute as ActivatedRoute; } get activatedRouteData() { if (this._activatedRoute) { return this._activatedRoute.snapshot.data; } return {}; } /** * Called when the `RouteReuseStrategy` instructs to detach the subtree */ detach(): ComponentRef { if (!this.activated) throw new Error('Outlet is not activated'); this.location.detach(); const cmp = this.activated; this.activated = null; this._activatedRoute = null; return cmp; } /** * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree */ attach(ref: ComponentRef, activatedRoute: ActivatedRoute) { this.activated = ref; this._activatedRoute = activatedRoute; this.location.insert(ref.hostView); } deactivate(): void { if (this.activated) { const c = this.component; this.activated.destroy(); this.activated = null; this._activatedRoute = null; this.deactivateEvents.emit(c); } } activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) { if (this.isActivated) { throw new Error('Cannot activate an already activated outlet'); } this._activatedRoute = activatedRoute; const snapshot = activatedRoute._futureSnapshot; const component = snapshot._routeConfig !.component; resolver = resolver || this.resolver; const factory = resolver.resolveComponentFactory(component); const childContexts = this.parentContexts.getOrCreateContext(this.name).children; const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector); this.activated = this.location.createComponent(factory, this.location.length, injector); // Calling `markForCheck` to make sure we will run the change detection when the // `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component. this.changeDetector.markForCheck(); this.activateEvents.emit(this.activated.instance); } } class OutletInjector implements Injector { constructor( private route: ActivatedRoute, private childContexts: ChildrenOutletContexts, private parent: Injector) {} get(token: any, notFoundValue?: any): any { if (token === ActivatedRoute) { return this.route; } if (token === ChildrenOutletContexts) { return this.childContexts; } return this.parent.get(token, notFoundValue); } }