diff --git a/src/cdk/portal/portal-directives.ts b/src/cdk/portal/portal-directives.ts index 64e00bdeceee..7bb40f3a2061 100644 --- a/src/cdk/portal/portal-directives.ts +++ b/src/cdk/portal/portal-directives.ts @@ -17,6 +17,8 @@ import { OnDestroy, OnInit, Input, + EventEmitter, + Output, } from '@angular/core'; import {Portal, TemplatePortal, ComponentPortal, BasePortalOutlet} from './portal'; @@ -35,6 +37,11 @@ export class CdkPortal extends TemplatePortal { } } +/** + * 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 @@ -52,6 +59,9 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr /** 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) { @@ -93,6 +103,14 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr this._attachedPortal = portal; } + @Output('attached') attached: EventEmitter = + new EventEmitter(); + + /** Component or view reference that is attached to the portal. */ + get attachedRef(): CdkPortalOutletAttachedRef { + return this._attachedRef; + } + ngOnInit() { this._isInitialized = true; } @@ -100,6 +118,7 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr ngOnDestroy() { super.dispose(); this._attachedPortal = null; + this._attachedRef = null; } /** @@ -125,6 +144,8 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr super.setDisposeFn(() => ref.destroy()); this._attachedPortal = portal; + this._attachedRef = ref; + this.attached.emit(ref); return ref; } @@ -140,6 +161,8 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr super.setDisposeFn(() => this._viewContainerRef.clear()); this._attachedPortal = portal; + this._attachedRef = viewRef; + this.attached.emit(viewRef); return viewRef; } diff --git a/src/cdk/portal/portal.spec.ts b/src/cdk/portal/portal.spec.ts index 0016acc7c267..349354cd69f6 100644 --- a/src/cdk/portal/portal.spec.ts +++ b/src/cdk/portal/portal.spec.ts @@ -10,7 +10,8 @@ import { Optional, Injector, ApplicationRef, - TemplateRef + TemplateRef, + ComponentRef, } from '@angular/core'; import {CommonModule} from '@angular/common'; import {CdkPortal, CdkPortalOutlet, PortalModule} from './portal-directives'; @@ -45,6 +46,9 @@ describe('Portals', () => { // Expect that the content of the attached portal is present. expect(hostContainer.textContent).toContain('Pizza'); expect(testAppComponent.portalOutlet.portal).toBe(componentPortal); + expect(testAppComponent.portalOutlet.attachedRef instanceof ComponentRef).toBe(true); + expect(testAppComponent.attachedSpy) + .toHaveBeenCalledWith(testAppComponent.portalOutlet.attachedRef); }); it('should load a template into the portal', () => { @@ -58,6 +62,13 @@ describe('Portals', () => { // Expect that the content of the attached portal is present and no context is projected expect(hostContainer.textContent).toContain('Banana'); expect(testAppComponent.portalOutlet.portal).toBe(templatePortal); + + // We can't test whether it's an instance of an `EmbeddedViewRef` so + // we verify that it's defined and that it's not a ComponentRef. + expect(testAppComponent.portalOutlet.attachedRef instanceof ComponentRef).toBe(false); + expect(testAppComponent.portalOutlet.attachedRef).toBeTruthy(); + expect(testAppComponent.attachedSpy) + .toHaveBeenCalledWith(testAppComponent.portalOutlet.attachedRef); }); it('should project template context bindings in the portal', () => { @@ -499,7 +510,7 @@ class ArbitraryViewContainerRefComponent { selector: 'portal-test', template: `
- +
Cake @@ -524,6 +535,7 @@ class PortalTestApp { selectedPortal: Portal|undefined; fruit: string = 'Banana'; fruits = ['Apple', 'Pineapple', 'Durian']; + attachedSpy = jasmine.createSpy('attached spy'); constructor(public injector: Injector) { }