Skip to content
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
26 changes: 20 additions & 6 deletions src/cdk/portal/portal-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
componentFactory, viewContainerRef.length,
portal.injector || viewContainerRef.injector);

// If we're using a view container that's different from the injected one (e.g. when the portal
// specifies its own) we need to move the component into the outlet, otherwise it'll be rendered
// inside of the alternate view container.
if (viewContainerRef !== this._viewContainerRef) {
this._getRootNode().appendChild((ref.hostView as EmbeddedViewRef<any>).rootNodes[0]);
}

super.setDisposeFn(() => ref.destroy());
this._attachedPortal = portal;
this._attachedRef = ref;
Expand Down Expand Up @@ -197,21 +204,28 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr

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

portal.setAttachedHost(this);
element.parentNode!.insertBefore(anchorNode, element);
rootNode.appendChild(element);
this._getRootNode().appendChild(element);

super.setDisposeFn(() => {
anchorNode.parentNode!.replaceChild(element, anchorNode);
});
}

/** Gets the root node of the portal outlet. */
private _getRootNode(): HTMLElement {
const nativeElement: Node = this._viewContainerRef.element.nativeElement;

// The directive could be set on a template which will result in a comment
// node being the root. Use the comment's parent node if that is the case.
return (nativeElement.nodeType === nativeElement.ELEMENT_NODE ?
nativeElement : nativeElement.parentNode!) as HTMLElement;
}

static ngAcceptInputType_portal: Portal<any> | null | undefined | '';
}

Expand Down
14 changes: 14 additions & 0 deletions src/cdk/portal/portal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,16 @@ describe('Portals', () => {
expect(spy).toHaveBeenCalled();
});

it('should render inside outlet when component portal specifies view container ref', () => {
const hostContainer = fixture.nativeElement.querySelector('.portal-container');
const portal = new ComponentPortal(PizzaMsg, fixture.componentInstance.alternateContainer);

fixture.componentInstance.selectedPortal = portal;
fixture.detectChanges();

expect(hostContainer.textContent).toContain('Pizza');
});

});

describe('DomPortalOutlet', () => {
Expand Down Expand Up @@ -593,6 +603,8 @@ class ArbitraryViewContainerRefComponent {
<ng-template [cdkPortalOutlet]="selectedPortal" (attached)="attachedSpy($event)"></ng-template>
</div>

<ng-container #alternateContainer></ng-container>

<ng-template cdk-portal>Cake</ng-template>

<div *cdk-portal>Pie</div>
Expand All @@ -616,6 +628,8 @@ class PortalTestApp {
@ViewChild(CdkPortalOutlet, {static: true}) portalOutlet: CdkPortalOutlet;
@ViewChild('templateRef', {read: TemplateRef, static: true}) templateRef: TemplateRef<any>;
@ViewChild('domPortalContent', {static: true}) domPortalContent: ElementRef<HTMLElement>;
@ViewChild('alternateContainer', {read: ViewContainerRef, static: true})
alternateContainer: ViewContainerRef;

selectedPortal: Portal<any>|undefined;
fruit: string = 'Banana';
Expand Down