Glossary

Portal mode

Moving a component's root into document.body while it is open, so it can't be clipped by overflow: hidden ancestors or trapped in a stacking context — then putting it back exactly where it was.

An anchored panel rendered inside a scrollable card or a transformed ancestor inherits its constraints: it gets cut off, or stacks under elements it should cover. Portal mode side-steps both by re-parenting the root to <body> for the time it is open. The popover uses it; modal is the other natural candidate.

On open

Before moving the root, the component drops a placeholder comment node in its place — that comment is the way home:

ng_popover.js
function open(trigger) {
    /* Portal mode: move the popover into <body> to escape
       overflow:hidden clipping and stacking-context issues */
    placeholder = document.createComment('ng-popover-portal');
    root.parentNode.insertBefore(placeholder, root);
    document.body.appendChild(root);
    // ...position, is-open, events...
}

On close

The root is re-inserted before the placeholder, and the placeholder is removed. The root must never be left orphaned in <body>:

ng_popover.js
function close() {
    // ...is-open off, focus back to trigger...

    /* Restore the original DOM position */
    if (placeholder && placeholder.parentNode) {
        placeholder.parentNode.insertBefore(root, placeholder);
        placeholder.remove();
        placeholder = null;
    }
}

Interaction with the observer

To a MutationObserver, a re-parented node looks like a removal from its old parent — and the observer's job is to unmount removed components. The guard: a node still connected to the document was moved, not removed, so the unmount is skipped:

ng_observer.js
// ng_observer.js — _flushRemoved()
if (document.contains(removed)) return; // moved, not removed → skip unmount

Any new component adopting portal mode must be checked against this path — see Component lifecycle.

Rules of thumb

WhenOnly when clipping or stacking is a real risk (anchored overlays). Not a default for every component.
PlaceholderAlways a comment node in the original position; restore on close, remove the placeholder.
CleanupInline positioning styles set while open are reset on close.
ObserverRe-parenting must stay invisible to the unmount path (moved ≠ removed).

Related

ng-popover is-open ng:popover:open ng:popover:close document.contains()