Skip to content

Commit b626b13

Browse files
crisbetojelbourn
authored andcommitted
feat(portal): expose attached result in CdkPortalOutlet (#9326)
Exposes the attach `ComponentRef` or `EmbeddedViewRef` in the `CdkPortalOutlet` directive. Fixes #9304.
1 parent a72085b commit b626b13

File tree

2 files changed

+37
-2
lines changed

2 files changed

+37
-2
lines changed

src/cdk/portal/portal-directives.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
OnDestroy,
1818
OnInit,
1919
Input,
20+
EventEmitter,
21+
Output,
2022
} from '@angular/core';
2123
import {Portal, TemplatePortal, ComponentPortal, BasePortalOutlet} from './portal';
2224

@@ -35,6 +37,11 @@ export class CdkPortal extends TemplatePortal<any> {
3537
}
3638
}
3739

40+
/**
41+
* Possible attached references to the CdkPortalOutlet.
42+
*/
43+
export type CdkPortalOutletAttachedRef = ComponentRef<any> | EmbeddedViewRef<any> | null;
44+
3845

3946
/**
4047
* 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
5259
/** Whether the portal component is initialized. */
5360
private _isInitialized = false;
5461

62+
/** Reference to the currently-attached component/view ref. */
63+
private _attachedRef: CdkPortalOutletAttachedRef;
64+
5565
constructor(
5666
private _componentFactoryResolver: ComponentFactoryResolver,
5767
private _viewContainerRef: ViewContainerRef) {
@@ -93,13 +103,22 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
93103
this._attachedPortal = portal;
94104
}
95105

106+
@Output('attached') attached: EventEmitter<CdkPortalOutletAttachedRef> =
107+
new EventEmitter<CdkPortalOutletAttachedRef>();
108+
109+
/** Component or view reference that is attached to the portal. */
110+
get attachedRef(): CdkPortalOutletAttachedRef {
111+
return this._attachedRef;
112+
}
113+
96114
ngOnInit() {
97115
this._isInitialized = true;
98116
}
99117

100118
ngOnDestroy() {
101119
super.dispose();
102120
this._attachedPortal = null;
121+
this._attachedRef = null;
103122
}
104123

105124
/**
@@ -125,6 +144,8 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
125144

126145
super.setDisposeFn(() => ref.destroy());
127146
this._attachedPortal = portal;
147+
this._attachedRef = ref;
148+
this.attached.emit(ref);
128149

129150
return ref;
130151
}
@@ -140,6 +161,8 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
140161
super.setDisposeFn(() => this._viewContainerRef.clear());
141162

142163
this._attachedPortal = portal;
164+
this._attachedRef = viewRef;
165+
this.attached.emit(viewRef);
143166

144167
return viewRef;
145168
}

src/cdk/portal/portal.spec.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
Optional,
1111
Injector,
1212
ApplicationRef,
13-
TemplateRef
13+
TemplateRef,
14+
ComponentRef,
1415
} from '@angular/core';
1516
import {CommonModule} from '@angular/common';
1617
import {CdkPortal, CdkPortalOutlet, PortalModule} from './portal-directives';
@@ -45,6 +46,9 @@ describe('Portals', () => {
4546
// Expect that the content of the attached portal is present.
4647
expect(hostContainer.textContent).toContain('Pizza');
4748
expect(testAppComponent.portalOutlet.portal).toBe(componentPortal);
49+
expect(testAppComponent.portalOutlet.attachedRef instanceof ComponentRef).toBe(true);
50+
expect(testAppComponent.attachedSpy)
51+
.toHaveBeenCalledWith(testAppComponent.portalOutlet.attachedRef);
4852
});
4953

5054
it('should load a template into the portal', () => {
@@ -58,6 +62,13 @@ describe('Portals', () => {
5862
// Expect that the content of the attached portal is present and no context is projected
5963
expect(hostContainer.textContent).toContain('Banana');
6064
expect(testAppComponent.portalOutlet.portal).toBe(templatePortal);
65+
66+
// We can't test whether it's an instance of an `EmbeddedViewRef` so
67+
// we verify that it's defined and that it's not a ComponentRef.
68+
expect(testAppComponent.portalOutlet.attachedRef instanceof ComponentRef).toBe(false);
69+
expect(testAppComponent.portalOutlet.attachedRef).toBeTruthy();
70+
expect(testAppComponent.attachedSpy)
71+
.toHaveBeenCalledWith(testAppComponent.portalOutlet.attachedRef);
6172
});
6273

6374
it('should project template context bindings in the portal', () => {
@@ -499,7 +510,7 @@ class ArbitraryViewContainerRefComponent {
499510
selector: 'portal-test',
500511
template: `
501512
<div class="portal-container">
502-
<ng-template [cdkPortalOutlet]="selectedPortal"></ng-template>
513+
<ng-template [cdkPortalOutlet]="selectedPortal" (attached)="attachedSpy($event)"></ng-template>
503514
</div>
504515
505516
<ng-template cdk-portal>Cake</ng-template>
@@ -524,6 +535,7 @@ class PortalTestApp {
524535
selectedPortal: Portal<any>|undefined;
525536
fruit: string = 'Banana';
526537
fruits = ['Apple', 'Pineapple', 'Durian'];
538+
attachedSpy = jasmine.createSpy('attached spy');
527539

528540
constructor(public injector: Injector) { }
529541

0 commit comments

Comments
 (0)