Skip to content

Commit f91b98f

Browse files
authored
feat(cdk/portal): allow specifying injector for template portal (#24554)
1 parent e1f8a9c commit f91b98f

File tree

5 files changed

+60
-22
lines changed

5 files changed

+60
-22
lines changed

src/cdk/portal/dom-portal-outlet.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
*/
88

99
import {
10+
ApplicationRef,
1011
ComponentFactoryResolver,
1112
ComponentRef,
1213
EmbeddedViewRef,
13-
ApplicationRef,
1414
Injector,
1515
} from '@angular/core';
16-
import {BasePortalOutlet, ComponentPortal, TemplatePortal, DomPortal} from './portal';
16+
import {BasePortalOutlet, ComponentPortal, DomPortal, TemplatePortal} from './portal';
1717

1818
/**
1919
* A PortalOutlet for attaching portals to an arbitrary DOM element outside of the Angular
@@ -84,7 +84,9 @@ export class DomPortalOutlet extends BasePortalOutlet {
8484
*/
8585
attachTemplatePortal<C>(portal: TemplatePortal<C>): EmbeddedViewRef<C> {
8686
let viewContainer = portal.viewContainerRef;
87-
let viewRef = viewContainer.createEmbeddedView(portal.templateRef, portal.context);
87+
let viewRef = viewContainer.createEmbeddedView(portal.templateRef, portal.context, {
88+
injector: portal.injector,
89+
});
8890

8991
// The method `createEmbeddedView` will add the view as a child of the viewContainer.
9092
// But for the DomPortalOutlet the view can be added everywhere in the DOM

src/cdk/portal/portal-directives.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,9 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
181181
*/
182182
attachTemplatePortal<C>(portal: TemplatePortal<C>): EmbeddedViewRef<C> {
183183
portal.setAttachedHost(this);
184-
const viewRef = this._viewContainerRef.createEmbeddedView(portal.templateRef, portal.context);
184+
const viewRef = this._viewContainerRef.createEmbeddedView(portal.templateRef, portal.context, {
185+
injector: portal.injector,
186+
});
185187
super.setDisposeFn(() => this._viewContainerRef.clear());
186188

187189
this._attachedPortal = portal;

src/cdk/portal/portal.spec.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ describe('Portals', () => {
6666
);
6767
});
6868

69-
it('should load a template into the portal', () => {
69+
it('should load a template into the portal outlet', () => {
7070
let testAppComponent = fixture.componentInstance;
7171
let hostContainer = fixture.nativeElement.querySelector('.portal-container');
7272
let templatePortal = new TemplatePortal(testAppComponent.templateRef, null!);
@@ -76,6 +76,36 @@ describe('Portals', () => {
7676

7777
// Expect that the content of the attached portal is present and no context is projected
7878
expect(hostContainer.textContent).toContain('Banana');
79+
expect(hostContainer.textContent).toContain('Pizza');
80+
expect(hostContainer.textContent).not.toContain('Chocolate');
81+
expect(testAppComponent.portalOutlet.portal).toBe(templatePortal);
82+
83+
// We can't test whether it's an instance of an `EmbeddedViewRef` so
84+
// we verify that it's defined and that it's not a ComponentRef.
85+
expect(testAppComponent.portalOutlet.attachedRef instanceof ComponentRef).toBe(false);
86+
expect(testAppComponent.portalOutlet.attachedRef).toBeTruthy();
87+
expect(testAppComponent.attachedSpy).toHaveBeenCalledWith(
88+
testAppComponent.portalOutlet.attachedRef,
89+
);
90+
});
91+
92+
it('should load a template with a custom injector into the portal outlet', () => {
93+
const testAppComponent = fixture.componentInstance;
94+
const hostContainer = fixture.nativeElement.querySelector('.portal-container');
95+
const templatePortal = new TemplatePortal(
96+
testAppComponent.templateRef,
97+
null!,
98+
undefined,
99+
new ChocolateInjector(fixture.componentInstance.injector),
100+
);
101+
102+
testAppComponent.selectedPortal = templatePortal;
103+
fixture.detectChanges();
104+
105+
// Expect that the content of the attached portal is present and no context is projected
106+
expect(hostContainer.textContent).toContain('Banana');
107+
expect(hostContainer.textContent).toContain('Pizza');
108+
expect(hostContainer.textContent).toContain('Chocolate');
79109
expect(testAppComponent.portalOutlet.portal).toBe(templatePortal);
80110

81111
// We can't test whether it's an instance of an `EmbeddedViewRef` so
@@ -264,6 +294,8 @@ describe('Portals', () => {
264294
// Expect that the content of the attached portal is present.
265295
let hostContainer = fixture.nativeElement.querySelector('.portal-container');
266296
expect(hostContainer.textContent).toContain('Banana');
297+
expect(hostContainer.textContent).toContain('Pizza');
298+
expect(hostContainer.textContent).not.toContain('Chocolate');
267299

268300
// When updating the binding value.
269301
testAppComponent.fruit = 'Mango';
@@ -750,15 +782,15 @@ class ArbitraryViewContainerRefComponent {
750782
<ng-template cdk-portal>Cake</ng-template>
751783
752784
<div *cdk-portal>Pie</div>
753-
<ng-template cdk-portal let-data> {{fruit}} - {{ data?.status }} </ng-template>
785+
<ng-template cdk-portal let-data> {{fruit}} - {{ data?.status }}! <pizza-msg></pizza-msg></ng-template>
754786
755787
<ng-template cdk-portal>
756788
<ul>
757789
<li *ngFor="let fruitName of fruits"> {{fruitName}} </li>
758790
</ul>
759791
</ng-template>
760792
761-
<ng-template #templateRef let-data> {{fruit}} - {{ data?.status }}!</ng-template>
793+
<ng-template #templateRef let-data> {{fruit}} - {{ data?.status }}! <pizza-msg></pizza-msg></ng-template>
762794
763795
<div class="dom-portal-parent">
764796
<div #domPortalContent>

src/cdk/portal/portal.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -119,20 +119,17 @@ export class ComponentPortal<T> extends Portal<ComponentRef<T>> {
119119
* A `TemplatePortal` is a portal that represents some embedded template (TemplateRef).
120120
*/
121121
export class TemplatePortal<C = any> extends Portal<EmbeddedViewRef<C>> {
122-
/** The embedded template that will be used to instantiate an embedded View in the host. */
123-
templateRef: TemplateRef<C>;
124-
125-
/** Reference to the ViewContainer into which the template will be stamped out. */
126-
viewContainerRef: ViewContainerRef;
127-
128-
/** Contextual data to be passed in to the embedded view. */
129-
context: C | undefined;
130-
131-
constructor(template: TemplateRef<C>, viewContainerRef: ViewContainerRef, context?: C) {
122+
constructor(
123+
/** The embedded template that will be used to instantiate an embedded View in the host. */
124+
public templateRef: TemplateRef<C>,
125+
/** Reference to the ViewContainer into which the template will be stamped out. */
126+
public viewContainerRef: ViewContainerRef,
127+
/** Contextual data to be passed in to the embedded view. */
128+
public context?: C,
129+
/** The injector to use for the embedded view. */
130+
public injector?: Injector,
131+
) {
132132
super();
133-
this.templateRef = template;
134-
this.viewContainerRef = viewContainerRef;
135-
this.context = context;
136133
}
137134

138135
get origin(): ElementRef {

tools/public_api_guard/cdk/portal.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,16 @@ export interface PortalOutlet {
160160

161161
// @public
162162
export class TemplatePortal<C = any> extends Portal<EmbeddedViewRef<C>> {
163-
constructor(template: TemplateRef<C>, viewContainerRef: ViewContainerRef, context?: C);
163+
constructor(
164+
templateRef: TemplateRef<C>,
165+
viewContainerRef: ViewContainerRef,
166+
context?: C | undefined,
167+
injector?: Injector | undefined);
164168
attach(host: PortalOutlet, context?: C | undefined): EmbeddedViewRef<C>;
165-
context: C | undefined;
169+
context?: C | undefined;
166170
// (undocumented)
167171
detach(): void;
172+
injector?: Injector | undefined;
168173
// (undocumented)
169174
get origin(): ElementRef;
170175
templateRef: TemplateRef<C>;

0 commit comments

Comments
 (0)