Skip to content

Commit

Permalink
feat(portal): expose attached result in CdkPortalOutlet (#9326)
Browse files Browse the repository at this point in the history
Exposes the attach `ComponentRef` or `EmbeddedViewRef` in the `CdkPortalOutlet` directive.

Fixes #9304.
  • Loading branch information
crisbeto authored and jelbourn committed Jan 23, 2018
1 parent a72085b commit b626b13
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 2 deletions.
23 changes: 23 additions & 0 deletions src/cdk/portal/portal-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
OnDestroy,
OnInit,
Input,
EventEmitter,
Output,
} from '@angular/core';
import {Portal, TemplatePortal, ComponentPortal, BasePortalOutlet} from './portal';

Expand All @@ -35,6 +37,11 @@ export class CdkPortal extends TemplatePortal<any> {
}
}

/**
* Possible attached references to the CdkPortalOutlet.
*/
export type CdkPortalOutletAttachedRef = ComponentRef<any> | EmbeddedViewRef<any> | null;


/**
* Directive version of a PortalOutlet. Because the directive *is* a PortalOutlet, portals can be
Expand All @@ -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) {
Expand Down Expand Up @@ -93,13 +103,22 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
this._attachedPortal = portal;
}

@Output('attached') attached: EventEmitter<CdkPortalOutletAttachedRef> =
new EventEmitter<CdkPortalOutletAttachedRef>();

/** 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;
}

/**
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
16 changes: 14 additions & 2 deletions src/cdk/portal/portal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand Down Expand Up @@ -499,7 +510,7 @@ class ArbitraryViewContainerRefComponent {
selector: 'portal-test',
template: `
<div class="portal-container">
<ng-template [cdkPortalOutlet]="selectedPortal"></ng-template>
<ng-template [cdkPortalOutlet]="selectedPortal" (attached)="attachedSpy($event)"></ng-template>
</div>
<ng-template cdk-portal>Cake</ng-template>
Expand All @@ -524,6 +535,7 @@ class PortalTestApp {
selectedPortal: Portal<any>|undefined;
fruit: string = 'Banana';
fruits = ['Apple', 'Pineapple', 'Durian'];
attachedSpy = jasmine.createSpy('attached spy');

constructor(public injector: Injector) { }

Expand Down

0 comments on commit b626b13

Please sign in to comment.