Why this exists
CDK creates a single overlay container at the document root, and every CDK consumer (NGWR, Angular Material, NG-ZORRO) attaches its overlays to that one element. Styling collisions are the usual symptom — z-index wars, shared .cdk-overlay-pane rules. provideWrOverlay() replaces two tokens NGWR components inject instead of CDK's own: WR_OVERLAY_CONTAINER with a WrOverlayContainer subclass that tags its element .wr-overlay-container, and WR_OVERLAY with a fresh Overlay built in a child injector bound to that container. It also installs WrVisualViewport, which publishes --wr-keyboard-inset so a bottom-sheet can lift clear of the on-screen keyboard.
import { bootstrapApplication } from '@angular/platform-browser';
import { provideWrOverlay } from 'ngwr/overlay';
bootstrapApplication(AppComponent, {
providers: [
provideWrOverlay(),
],
});Scope styles to NGWR overlays
After provideWrOverlay() is registered, the overlay root element has both the standard .cdk-overlay-container class and .wr-overlay-container. Use the second to scope your overrides:
// Scope custom overlay styles to NGWR overlays so other libs aren't affected.
.wr-overlay-container .cdk-overlay-pane {
z-index: 1100;
}Caveats
CDK's own root Overlay and OverlayContainer are left untouched, so other libraries keep the container they already had — the isolation is NGWR moving out, not NGWR taking over. What the call does not do is isolate two OTHER libraries from each other. And it is not load-bearing: both tokens fall back to CDK's root instances, so overlays still open without it — they just land in the shared container, and the keyboard inset is never published.
Focus, on the way in
A modal overlay — WrDialog, <wr-drawer> and WrDrawerManager — records document.activeElement before it attaches, installs a CDK focus trap once the content is in the DOM, and moves focus inside. What it moves focus TO is decided in this order.
- The element carrying
cdkFocusInitial, if the content has one. It is a plain attribute — no import, no directive — because the trap resolves it withquerySelector. This is the answer to "focus has to land on Cancel, not on Delete"; nothing needs to callfocus()afteropen(). - Otherwise the first tabbable element inside the overlay, in DOM order.
- Otherwise nothing moves, and the trap still holds.
Which means the two drawer forms differ, and the difference is DOM order rather than policy: <wr-drawer> renders its ✕ in its own template, above the projected content, so the ✕ is the first tabbable and takes focus; WrDialog and WrDrawerManager append the ✕ AFTER the content portal — so it paints above the panel — and focus lands on the content's first control. Pin it with cdkFocusInitial rather than relying on either.
<!-- Inside the dialog's own component template. `cdkFocusInitial` is a plain
attribute the CDK focus trap looks up — nothing to import. -->
<h2 wrDialogTitle>Delete project</h2>
<p>This cannot be undone.</p>
<footer>
<button wr-btn cdkFocusInitial [wrDialogClose]="false">Cancel</button>
<button wr-btn color="danger" [wrDialogClose]="true">Delete</button>
</footer>Focus, on the way out
Every dismissal path funnels through one close(), and that one method destroys the trap, disposes the overlay and then restores focus to the element recorded on open. There is no path that skips it, and no option that turns it off.
| Closed by | Focus goes to | Note |
|---|---|---|
| The opener | Off with closeOnEscape: false. Focus need not be inside the overlay — the CDK keeps one document listener and routes the key to the topmost overlay. | |
| The built-in ✕ | The opener | Off with closable: false; renamed with closeLabel. |
[wrDialogClose] / ref.close(result) | The opener | The same method the ✕ calls, so the result is the only difference. |
| Backdrop click | The opener | Off with closeOnBackdropClick: false. |
A fifth path is not a dismissal: with closeOnNavigation left on, a route change disposes the overlay so a modal cannot outlive the page it was opened over. And a dismissal is not distinguishable fromclose(undefined) — both resolve awaitClose() with undefined, so a dialog that needs to tell "cancelled" from "saved nothing" has to close with a value that says which.
Components that use the overlay
// Overlay-based building blocks shipped by the lib:
import { WrDialog } from 'ngwr/dialog';
import { WrToast } from 'ngwr/toast';
import { WrDrawer } from 'ngwr/drawer';
import { WrPopover } from 'ngwr/popover'; // also covers tooltip via mode="tooltip"
import { WrPopconfirm } from 'ngwr/popconfirm';
import { WrDropdown } from 'ngwr/dropdown';
import { WrSelect } from 'ngwr/select';