/** * @license * Copyright Google LLC 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 { ComponentFactoryResolver, ComponentRef, Directive, EmbeddedViewRef, EventEmitter, NgModule, OnDestroy, OnInit, Output, TemplateRef, ViewContainerRef, } from '@angular/core'; import {BasePortalOutlet, ComponentPortal, Portal, TemplatePortal} from './portal'; /** * Directive version of a `TemplatePortal`. Because the directive *is* a TemplatePortal, * the directive instance itself can be attached to a host, enabling declarative use of portals. */ @Directive({ selector: '[cdkPortal]', exportAs: 'cdkPortal', }) export class CdkPortal extends TemplatePortal { constructor(templateRef: TemplateRef, viewContainerRef: ViewContainerRef) { super(templateRef, viewContainerRef); } } /** * @deprecated Use `CdkPortal` instead. * @breaking-change 9.0.0 */ @Directive({ selector: '[cdk-portal], [portal]', exportAs: 'cdkPortal', providers: [{ provide: CdkPortal, useExisting: TemplatePortalDirective }] }) export class TemplatePortalDirective extends CdkPortal {} /** * Possible attached references to the CdkPortalOutlet. */ export type CdkPortalOutletAttachedRef = ComponentRef | EmbeddedViewRef | null; /** * Directive version of a PortalOutlet. Because the directive *is* a PortalOutlet, portals can be * directly attached to it, enabling declarative use. * * Usage: * `` */ @Directive({ selector: '[cdkPortalOutlet]', exportAs: 'cdkPortalOutlet', inputs: ['portal: cdkPortalOutlet'] }) export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestroy { /** Whether the portal component is initialized. */ private _isInitialized = false; /** Reference to the currently-attached component/view ref. */ private _attachedRef: CdkPortalOutletAttachedRef; constructor( private _componentFactoryResolver: ComponentFactoryResolver, private _viewContainerRef: ViewContainerRef) { super(); } /** Portal associated with the Portal outlet. */ get portal(): Portal | null { return this._attachedPortal; } set portal(portal: Portal | null) { // Ignore the cases where the `portal` is set to a falsy value before the lifecycle hooks have // run. This handles the cases where the user might do something like `
` // and attach a portal programmatically in the parent component. When Angular does the first CD // round, it will fire the setter with empty string, causing the user's content to be cleared. if (this.hasAttached() && !portal && !this._isInitialized) { return; } if (this.hasAttached()) { super.detach(); } if (portal) { super.attach(portal); } this._attachedPortal = portal; } /** Emits when a portal is attached to the outlet. */ @Output() attached: EventEmitter = new EventEmitter(); /** Component or view reference that is attached to the portal. */ get attachedRef(): CdkPortalOutletAttachedRef { return this._attachedRef; } ngOnInit() { this._isInitialized = true; } ngOnDestroy() { super.dispose(); this._attachedPortal = null; this._attachedRef = null; } /** * Attach the given ComponentPortal to this PortalOutlet using the ComponentFactoryResolver. * * @param portal Portal to be attached to the portal outlet. * @returns Reference to the created component. */ attachComponentPortal(portal: ComponentPortal): ComponentRef { portal.setAttachedHost(this); // If the portal specifies an origin, use that as the logical location of the component // in the application tree. Otherwise use the location of this PortalOutlet. const viewContainerRef = portal.viewContainerRef != null ? portal.viewContainerRef : this._viewContainerRef; const resolver = portal.componentFactoryResolver || this._componentFactoryResolver; const componentFactory = resolver.resolveComponentFactory(portal.component); const ref = viewContainerRef.createComponent( componentFactory, viewContainerRef.length, portal.injector || viewContainerRef.injector); super.setDisposeFn(() => ref.destroy()); this._attachedPortal = portal; this._attachedRef = ref; this.attached.emit(ref); return ref; } /** * Attach the given TemplatePortal to this PortlHost as an embedded View. * @param portal Portal to be attached. * @returns Reference to the created embedded view. */ attachTemplatePortal(portal: TemplatePortal): EmbeddedViewRef { portal.setAttachedHost(this); const viewRef = this._viewContainerRef.createEmbeddedView(portal.templateRef, portal.context); super.setDisposeFn(() => this._viewContainerRef.clear()); this._attachedPortal = portal; this._attachedRef = viewRef; this.attached.emit(viewRef); return viewRef; } } /** * @deprecated Use `CdkPortalOutlet` instead. * @breaking-change 9.0.0 */ @Directive({ selector: '[cdkPortalHost], [portalHost]', exportAs: 'cdkPortalHost', inputs: ['portal: cdkPortalHost'], providers: [{ provide: CdkPortalOutlet, useExisting: PortalHostDirective }] }) export class PortalHostDirective extends CdkPortalOutlet {} @NgModule({ exports: [CdkPortal, CdkPortalOutlet, TemplatePortalDirective, PortalHostDirective], declarations: [CdkPortal, CdkPortalOutlet, TemplatePortalDirective, PortalHostDirective], }) export class PortalModule {}