Skip to content

Commit 6d08240

Browse files
crisbetojelbourn
authored andcommitted
fix(portal): content not rendered inside outlet when view container is provided (#17731)
Fixes the contents of the portal not being rendered inside of the `CdkPortalOutlet` if an alternate `ViewContainerRef` is provided. We have some logic that moves the content into the outlet for `DomPortalOutlet` already, but we probably missed adding it to the directive since it isn't being used in as many places. Fixes #17650.
1 parent 1bba82d commit 6d08240

File tree

2 files changed

+34
-6
lines changed

2 files changed

+34
-6
lines changed

src/cdk/portal/portal-directives.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
157157
componentFactory, viewContainerRef.length,
158158
portal.injector || viewContainerRef.injector);
159159

160+
// If we're using a view container that's different from the injected one (e.g. when the portal
161+
// specifies its own) we need to move the component into the outlet, otherwise it'll be rendered
162+
// inside of the alternate view container.
163+
if (viewContainerRef !== this._viewContainerRef) {
164+
this._getRootNode().appendChild((ref.hostView as EmbeddedViewRef<any>).rootNodes[0]);
165+
}
166+
160167
super.setDisposeFn(() => ref.destroy());
161168
this._attachedPortal = portal;
162169
this._attachedRef = ref;
@@ -197,21 +204,28 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
197204

198205
// Anchor used to save the element's previous position so
199206
// that we can restore it when the portal is detached.
200-
let anchorNode = this._document.createComment('dom-portal');
201-
let element = portal.element;
202-
const nativeElement: Node = this._viewContainerRef.element.nativeElement;
203-
const rootNode = nativeElement.nodeType === nativeElement.ELEMENT_NODE ?
204-
nativeElement : nativeElement.parentNode!;
207+
const anchorNode = this._document.createComment('dom-portal');
208+
const element = portal.element;
205209

206210
portal.setAttachedHost(this);
207211
element.parentNode!.insertBefore(anchorNode, element);
208-
rootNode.appendChild(element);
212+
this._getRootNode().appendChild(element);
209213

210214
super.setDisposeFn(() => {
211215
anchorNode.parentNode!.replaceChild(element, anchorNode);
212216
});
213217
}
214218

219+
/** Gets the root node of the portal outlet. */
220+
private _getRootNode(): HTMLElement {
221+
const nativeElement: Node = this._viewContainerRef.element.nativeElement;
222+
223+
// The directive could be set on a template which will result in a comment
224+
// node being the root. Use the comment's parent node if that is the case.
225+
return (nativeElement.nodeType === nativeElement.ELEMENT_NODE ?
226+
nativeElement : nativeElement.parentNode!) as HTMLElement;
227+
}
228+
215229
static ngAcceptInputType_portal: Portal<any> | null | undefined | '';
216230
}
217231

src/cdk/portal/portal.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,16 @@ describe('Portals', () => {
361361
expect(spy).toHaveBeenCalled();
362362
});
363363

364+
it('should render inside outlet when component portal specifies view container ref', () => {
365+
const hostContainer = fixture.nativeElement.querySelector('.portal-container');
366+
const portal = new ComponentPortal(PizzaMsg, fixture.componentInstance.alternateContainer);
367+
368+
fixture.componentInstance.selectedPortal = portal;
369+
fixture.detectChanges();
370+
371+
expect(hostContainer.textContent).toContain('Pizza');
372+
});
373+
364374
});
365375

366376
describe('DomPortalOutlet', () => {
@@ -593,6 +603,8 @@ class ArbitraryViewContainerRefComponent {
593603
<ng-template [cdkPortalOutlet]="selectedPortal" (attached)="attachedSpy($event)"></ng-template>
594604
</div>
595605
606+
<ng-container #alternateContainer></ng-container>
607+
596608
<ng-template cdk-portal>Cake</ng-template>
597609
598610
<div *cdk-portal>Pie</div>
@@ -616,6 +628,8 @@ class PortalTestApp {
616628
@ViewChild(CdkPortalOutlet, {static: true}) portalOutlet: CdkPortalOutlet;
617629
@ViewChild('templateRef', {read: TemplateRef, static: true}) templateRef: TemplateRef<any>;
618630
@ViewChild('domPortalContent', {static: true}) domPortalContent: ElementRef<HTMLElement>;
631+
@ViewChild('alternateContainer', {read: ViewContainerRef, static: true})
632+
alternateContainer: ViewContainerRef;
619633

620634
selectedPortal: Portal<any>|undefined;
621635
fruit: string = 'Banana';

0 commit comments

Comments
 (0)