Skip to content

feat(cdk/portal): allow specifying injector for template portal #24554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/cdk/portal/dom-portal-outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
*/

import {
ApplicationRef,
ComponentFactoryResolver,
ComponentRef,
EmbeddedViewRef,
ApplicationRef,
Injector,
} from '@angular/core';
import {BasePortalOutlet, ComponentPortal, TemplatePortal, DomPortal} from './portal';
import {BasePortalOutlet, ComponentPortal, DomPortal, TemplatePortal} from './portal';

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

// The method `createEmbeddedView` will add the view as a child of the viewContainer.
// But for the DomPortalOutlet the view can be added everywhere in the DOM
Expand Down
4 changes: 3 additions & 1 deletion src/cdk/portal/portal-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,9 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
*/
attachTemplatePortal<C>(portal: TemplatePortal<C>): EmbeddedViewRef<C> {
portal.setAttachedHost(this);
const viewRef = this._viewContainerRef.createEmbeddedView(portal.templateRef, portal.context);
const viewRef = this._viewContainerRef.createEmbeddedView(portal.templateRef, portal.context, {
injector: portal.injector,
});
Comment on lines +184 to +186
Copy link
Contributor

@lekhmanrus lekhmanrus Apr 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be

    const viewRef = this._viewContainerRef.createEmbeddedView(
      portal.templateRef,
      portal.context,
      portal.injector ? { injector: portal.injector } : undefined
    );

instead?

See StackBlitz. When we click Render template portal button it throws an error:
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't reproduce this error locally in our dev app. I think the problem is that your Stackblitz is running v14 of the CDK against v13 of the Framework which doesn't support the injector option yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crisbeto, right. Sorry about that.

super.setDisposeFn(() => this._viewContainerRef.clear());

this._attachedPortal = portal;
Expand Down
38 changes: 35 additions & 3 deletions src/cdk/portal/portal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('Portals', () => {
);
});

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

// Expect that the content of the attached portal is present and no context is projected
expect(hostContainer.textContent).toContain('Banana');
expect(hostContainer.textContent).toContain('Pizza');
expect(hostContainer.textContent).not.toContain('Chocolate');
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 load a template with a custom injector into the portal outlet', () => {
const testAppComponent = fixture.componentInstance;
const hostContainer = fixture.nativeElement.querySelector('.portal-container');
const templatePortal = new TemplatePortal(
testAppComponent.templateRef,
null!,
undefined,
new ChocolateInjector(fixture.componentInstance.injector),
);

testAppComponent.selectedPortal = templatePortal;
fixture.detectChanges();

// Expect that the content of the attached portal is present and no context is projected
expect(hostContainer.textContent).toContain('Banana');
expect(hostContainer.textContent).toContain('Pizza');
expect(hostContainer.textContent).toContain('Chocolate');
expect(testAppComponent.portalOutlet.portal).toBe(templatePortal);

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

// When updating the binding value.
testAppComponent.fruit = 'Mango';
Expand Down Expand Up @@ -750,15 +782,15 @@ class ArbitraryViewContainerRefComponent {
<ng-template cdk-portal>Cake</ng-template>

<div *cdk-portal>Pie</div>
<ng-template cdk-portal let-data> {{fruit}} - {{ data?.status }} </ng-template>
<ng-template cdk-portal let-data> {{fruit}} - {{ data?.status }}! <pizza-msg></pizza-msg></ng-template>

<ng-template cdk-portal>
<ul>
<li *ngFor="let fruitName of fruits"> {{fruitName}} </li>
</ul>
</ng-template>

<ng-template #templateRef let-data> {{fruit}} - {{ data?.status }}!</ng-template>
<ng-template #templateRef let-data> {{fruit}} - {{ data?.status }}! <pizza-msg></pizza-msg></ng-template>

<div class="dom-portal-parent">
<div #domPortalContent>
Expand Down
23 changes: 10 additions & 13 deletions src/cdk/portal/portal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,20 +119,17 @@ export class ComponentPortal<T> extends Portal<ComponentRef<T>> {
* A `TemplatePortal` is a portal that represents some embedded template (TemplateRef).
*/
export class TemplatePortal<C = any> extends Portal<EmbeddedViewRef<C>> {
/** The embedded template that will be used to instantiate an embedded View in the host. */
templateRef: TemplateRef<C>;

/** Reference to the ViewContainer into which the template will be stamped out. */
viewContainerRef: ViewContainerRef;

/** Contextual data to be passed in to the embedded view. */
context: C | undefined;

constructor(template: TemplateRef<C>, viewContainerRef: ViewContainerRef, context?: C) {
constructor(
/** The embedded template that will be used to instantiate an embedded View in the host. */
public templateRef: TemplateRef<C>,
/** Reference to the ViewContainer into which the template will be stamped out. */
public viewContainerRef: ViewContainerRef,
/** Contextual data to be passed in to the embedded view. */
public context?: C,
/** The injector to use for the embedded view. */
public injector?: Injector,
) {
super();
this.templateRef = template;
this.viewContainerRef = viewContainerRef;
this.context = context;
}

get origin(): ElementRef {
Expand Down
9 changes: 7 additions & 2 deletions tools/public_api_guard/cdk/portal.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,16 @@ export interface PortalOutlet {

// @public
export class TemplatePortal<C = any> extends Portal<EmbeddedViewRef<C>> {
constructor(template: TemplateRef<C>, viewContainerRef: ViewContainerRef, context?: C);
constructor(
templateRef: TemplateRef<C>,
viewContainerRef: ViewContainerRef,
context?: C | undefined,
injector?: Injector | undefined);
attach(host: PortalOutlet, context?: C | undefined): EmbeddedViewRef<C>;
context: C | undefined;
context?: C | undefined;
// (undocumented)
detach(): void;
injector?: Injector | undefined;
// (undocumented)
get origin(): ElementRef;
templateRef: TemplateRef<C>;
Expand Down