diff --git a/src/cdk/portal/dom-portal-outlet.ts b/src/cdk/portal/dom-portal-outlet.ts
index 9c889f539689..78ed0f3df2d8 100644
--- a/src/cdk/portal/dom-portal-outlet.ts
+++ b/src/cdk/portal/dom-portal-outlet.ts
@@ -115,17 +115,23 @@ export class DomPortalOutlet extends BasePortalOutlet {
throw Error('Cannot attach DOM portal without _document constructor parameter');
}
+ const element = portal.element;
+ if (!element.parentNode) {
+ throw Error('DOM portal content must be attached to a parent node.');
+ }
+
// 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 anchorNode = this._document.createComment('dom-portal');
- element.parentNode!.insertBefore(anchorNode, element);
+ element.parentNode.insertBefore(anchorNode, element);
this.outletElement.appendChild(element);
super.setDisposeFn(() => {
// We can't use `replaceWith` here because IE doesn't support it.
- anchorNode.parentNode!.replaceChild(element, anchorNode);
+ if (anchorNode.parentNode) {
+ anchorNode.parentNode.replaceChild(element, anchorNode);
+ }
});
}
diff --git a/src/cdk/portal/portal-directives.ts b/src/cdk/portal/portal-directives.ts
index b1279458321e..2a45fed54306 100644
--- a/src/cdk/portal/portal-directives.ts
+++ b/src/cdk/portal/portal-directives.ts
@@ -202,17 +202,23 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
throw Error('Cannot attach DOM portal without _document constructor parameter');
}
+ const element = portal.element;
+ if (!element.parentNode) {
+ throw Error('DOM portal content must be attached to a parent node.');
+ }
+
// Anchor used to save the element's previous position so
// that we can restore it when the portal is detached.
const anchorNode = this._document.createComment('dom-portal');
- const element = portal.element;
portal.setAttachedHost(this);
- element.parentNode!.insertBefore(anchorNode, element);
+ element.parentNode.insertBefore(anchorNode, element);
this._getRootNode().appendChild(element);
super.setDisposeFn(() => {
- anchorNode.parentNode!.replaceChild(element, anchorNode);
+ if (anchorNode.parentNode) {
+ anchorNode.parentNode!.replaceChild(element, anchorNode);
+ }
});
}
diff --git a/src/cdk/portal/portal.spec.ts b/src/cdk/portal/portal.spec.ts
index f094218b4e9f..c9a1db266945 100644
--- a/src/cdk/portal/portal.spec.ts
+++ b/src/cdk/portal/portal.spec.ts
@@ -107,6 +107,33 @@ describe('Portals', () => {
.toBe(false, 'Expected content to be removed from outlet on detach.');
});
+ it('should throw when trying to load an element without a parent into a DOM portal', () => {
+ const testAppComponent = fixture.componentInstance;
+ const element = document.createElement('div');
+ const domPortal = new DomPortal(element);
+
+ expect(() => {
+ testAppComponent.selectedPortal = domPortal;
+ fixture.detectChanges();
+ }).toThrowError('DOM portal content must be attached to a parent node.');
+ });
+
+ it('should not throw when restoring if the outlet element was cleared', () => {
+ const testAppComponent = fixture.componentInstance;
+ const parent = fixture.nativeElement.querySelector('.dom-portal-parent');
+ const domPortal = new DomPortal(testAppComponent.domPortalContent);
+
+ testAppComponent.selectedPortal = domPortal;
+ fixture.detectChanges();
+
+ parent.innerHTML = '';
+
+ expect(() => {
+ testAppComponent.selectedPortal = undefined;
+ fixture.detectChanges();
+ }).not.toThrow();
+ });
+
it('should project template context bindings in the portal', () => {
let testAppComponent = fixture.componentInstance;
let hostContainer = fixture.nativeElement.querySelector('.portal-container');
@@ -558,6 +585,29 @@ describe('Portals', () => {
expect(someDomElement.textContent!.trim()).toBe('');
});
+ it('should throw when trying to load an element without a parent into a DOM portal', () => {
+ const fixture = TestBed.createComponent(PortalTestApp);
+ fixture.detectChanges();
+ const element = document.createElement('div');
+ const portal = new DomPortal(element);
+
+ expect(() => {
+ portal.attach(host);
+ fixture.detectChanges();
+ }).toThrowError('DOM portal content must be attached to a parent node.');
+ });
+
+ it('should not throw when restoring if the outlet element was cleared', () => {
+ const fixture = TestBed.createComponent(PortalTestApp);
+ fixture.detectChanges();
+ const portal = new DomPortal(fixture.componentInstance.domPortalContent);
+
+ portal.attach(host);
+ host.outletElement.innerHTML = '';
+
+ expect(() => host.detach()).not.toThrow();
+ });
+
});
});
@@ -618,8 +668,10 @@ class ArbitraryViewContainerRefComponent {
Hello there
+Hello there
+