angular/components · error

DOM portal content must be attached to a parent node.

Error message

DOM portal content must be attached to a parent node.

What it means

Same check as DomPortalOutlet, but in the CdkPortalOutlet directive: a DomPortal's element must have a parentNode when `attachDomPortal` runs, since the outlet uses an anchor comment to restore the element's original DOM position on detach.

Source

Thrown at src/cdk/portal/portal-directives.ts:182

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

    this._attachedPortal = portal;
    this._attachedRef = viewRef;
    this.attached.emit(viewRef);

    return viewRef;
  }

  /**
   * Attaches the given DomPortal to this PortalHost by moving all of the portal content into it.
   * @param portal Portal to be attached.
   * @deprecated To be turned into a method.
   * @breaking-change 10.0.0
   */
  override attachDomPortal = (portal: DomPortal) => {
    const element = portal.element;
    if (!element.parentNode && (typeof ngDevMode === 'undefined' || ngDevMode)) {
      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');

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

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

View on GitHub (pinned to 0411926e7d)

Solutions

  1. Ensure the source element is inserted in the DOM before constructing the DomPortal
  2. Attach the portal only after view init (ngAfterViewInit) when the element is in the tree
  3. Switch to TemplatePortal/ComponentPortal for non-DOM-hosted content
  4. Add an `el.isConnected` guard before calling attach

Example fix

// before
const portal = new DomPortal(this.detachedDiv);
this.outlet.attach(portal); // throws
// after
ngAfterViewInit() {
  if (!this.detachedDiv.parentNode) { this.hostRef.nativeElement.appendChild(this.detachedDiv); }
  this.outlet.attach(new DomPortal(this.detachedDiv));
}
Defensive patterns

Strategy: validation

Validate before calling

ngAfterViewInit() {
  if (this.sourceEl?.isConnected && this.outlet && !this.outlet.hasAttached()) {
    this.outlet.attach(new DomPortal(this.sourceEl));
  }
}

Type guard

function isAttachedToDom(el: Node | null | undefined): el is Node {
  return !!el && el.isConnected && el.parentNode != null;
}

Try / catch

try {
  outlet.attachDomPortal(portal);
} catch (e) {
  if (e.message.includes('must be attached to a parent node')) {
    outlet._document.body.appendChild(portal.element);
    outlet.attachDomPortal(portal);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Binding an element without a parent to a `<ng-template cdkPortalOutlet>` via a DomPortal, e.g. `outlet.attach(new DomPortal(detachedEl))`, including elements created in component code or parsed from strings.

Common situations: Angular app rendering detached elements created with renderer.createElement; passing content extracted from a detached template; host bindings that clear the element's parent before the portal attaches; SSR/hydration edge cases where the node is not yet in the tree.

Related errors


AI-assisted analysis of angular/components@0411926e7d (2026-08-31). Data as JSON: /api/errors/e05160ff71532907. Report an issue: GitHub.