import { Component } from '@angular/core';
import { WR_DIALOG_DATA, WrDialog, WrDialogClose, WrDialogContent, WrDialogFooter, WrDialogTitle } from 'ngwr/dialog';
@Component({ imports: [WrDialogClose, WrDialogContent, WrDialogFooter, WrDialogTitle] })
export class MyComponent {}Dialog
Open any component as a modal dialog and await its result.
Installation
Import the class, and @use the entry point in your global styles: without it the component renders unstyled.
Open a dialog
open() returns a ref; awaitClose() resolves with the value the dialog closed with.
const ref = dialog.open(ConfirmComponent, {
data: { title: 'Delete', message: 'Are you sure?' },
width: '24rem',
});
const ok = await ref.awaitClose();<!-- Inside the opened component.
imports: [WrDialogTitle, WrDialogContent, WrDialogFooter, WrDialogClose] -->
<h2 wrDialogTitle>Delete</h2>
<div wrDialogContent>Are you sure?</div>
<div wrDialogFooter>
<wr-btn wrDialogClose>Cancel</wr-btn>
<wr-btn color="danger" [wrDialogClose]="true">Delete</wr-btn>
</div>dialog.open(ConfirmComponent, { data, width: '24rem' })Closing from the content
Inject WrDialogRef and call close(result); [wrDialogClose] does the same on click.
import { WR_DIALOG_DATA, WrDialogRef } from 'ngwr/dialog';
@Component({...})
export class EditUserComponent {
private readonly ref = inject<WrDialogRef<EditUserComponent, boolean>>(WrDialogRef);
protected readonly data = inject<EditUserData>(WR_DIALOG_DATA);
save(): void {
this.store.saveUser(this.form.value);
this.ref.close(true);
}
}Lifetime
A dialog outlives the component that opened it. It closes on navigation; anything else is yours to close.
@Component({...})
export class MyComponent {
private readonly dialog = inject(WrDialog);
private readonly destroyRef = inject(DestroyRef);
async confirmDelete(): Promise<void> {
const ref = this.dialog.open<ConfirmComponent, boolean>(ConfirmComponent);
const stop = this.destroyRef.onDestroy(() => ref.close());
const ok = await ref.awaitClose();
stop();
if (ok) this.store.remove();
}
}
// Or subscribe:
ref.closed.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(ok => { … });The close button
A ✕ is shown by default. closable: false hides it, closeLabel renames it.
dialog.open(EditUserComponent, { closable: false });
dialog.open(EditUserComponent, { closeLabel: 'Discard changes' });Focus
Focus moves to cdkFocusInitial, else to the first tabbable element, and returns to the trigger on close.
<!-- cdkFocusInitial needs no import. -->
<h2 wrDialogTitle>Rename project</h2>
<div wrDialogContent>
<wr-form-field label="Name">
<input wrInput cdkFocusInitial [(value)]="name" />
</wr-form-field>
</div>
<div wrDialogFooter>
<wr-btn wrDialogClose>Cancel</wr-btn>
<wr-btn color="primary" [wrDialogClose]="name()">Save</wr-btn>
</div>Gotchas
| What happens | What to do |
|---|---|
| A click outside an open select inside the dialog closes the select and the dialog. | Open a dialog that holds a form with closeOnBackdropClick: false. |
No z-index puts your element above a dialog: overlays live in the top layer. | Make that element a popover too, or provide OVERLAY_DEFAULT_CONFIG with { usePopover: false }. |
An ngwr overlay inside a native <dialog> opened with showModal() ignores clicks. | Use WrDialog instead of the native dialog. |
If the dialog's result removes its trigger, focus falls back to <body>. | Move focus yourself after awaitClose(). |
Service API
| Name | Description | Type | Default |
|---|---|---|---|
open(component, options?) | Opens a dialog. Returns a WrDialogRef. | (component, WrDialogOptions) => WrDialogRef | — |
WrDialogRef
Every dismissal closes with undefined, so close with a value of your own to tell them apart.
| Name | Description | Type | Default |
|---|---|---|---|
WrDialogRef<C, R> | Returned by open() and injectable inside the dialog. C is the component, R the result. | class | — |
close(result?) | Close the dialog, optionally with a result. A second call does nothing. | (result?: R) => void | — |
awaitClose() | Resolves with the close result. | () => Promise<R | undefined> | — |
closed | The same result as an Observable. Emits once, then completes; a late subscriber still gets it. | ReplaySubject<R | undefined> | — |
componentInstance | The opened component. Throws if read from its own constructor. | C | — |
overlayRef | The underlying CDK OverlayRef. Never dispose it; call close(). | OverlayRef | — |
const ref = this.dialog.open<ConfirmComponent, 'saved' | 'discarded'>(ConfirmComponent);
const result = await ref.awaitClose(); // 'saved' | 'discarded' | undefined
if (result === undefined) return; // dismissedWrDialogOptions
| Name | Description | Type | Default |
|---|---|---|---|
WrDialogOptions | Second argument of open(). Every field is optional. | interface | — |
data | Payload exposed to the content via WR_DIALOG_DATA. | D | — |
width | Panel width — any CSS length. | string | — |
maxWidth | Panel maximum width. | string | — |
closeOnBackdropClick | Close when the backdrop is clicked. | boolean | true |
closeOnEscape | Close on Escape. | boolean | true |
closeOnNavigation | Close when the URL changes. | boolean | true |
closable | Show the built-in dismiss (×) in the top-right corner. | boolean | true |
closeLabel | Accessible name for the dismiss button. Falls back to the dialog.close catalog key. | string | — |
panelClass | Extra class(es) on the panel. | string | readonly string[] | — |
Layout directives
| Name | Description | Type | Default |
|---|---|---|---|
[wrDialogTitle] | Styles the title row, and supplies the panel’s aria-labelledby. Import WrDialogTitle. | directive | — |
[wrDialogContent] | Styles the scrollable body. Import WrDialogContent. | directive | — |
[wrDialogFooter] | Styles the footer. Import WrDialogFooter. | directive | — |
align | Footer alignment. | 'start' | 'center' | 'end' | 'end' |
[wrDialogClose] | Closes the dialog when clicked. Import WrDialogClose. | directive | — |
wrDialogClose | Value passed to close(). Bare attribute closes with undefined. | R | undefined | undefined |
Available inside the dialog
| Name | Description | Type | Default |
|---|---|---|---|
WR_DIALOG_DATA | The data payload passed to open(). undefined when you didn't pass any. | InjectionToken<D> | — |
WrDialogRef | The open dialog’s own ref — call close(result) to dismiss it from the content. | WrDialogRef<unknown, unknown> | — |
WR_DIALOG_REF | The same ref, used by [wrDialogClose]. Prefer inject(WrDialogRef). | InjectionToken<WrDialogRef<C, R>> | — |
CSS variables
Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/dialog's own element.
| Variable | Default | Declared on |
|---|---|---|
--wr-dialog-bg | var(--wr-color-surface) | .wr-dialog-panel |
--wr-dialog-border | var(--wr-border-base) | .wr-dialog-panel |
--wr-dialog-close-font-size | var(--wr-text-base) | .wr-dialog-panel |
--wr-dialog-close-padding | var(--wr-space-xs) | .wr-dialog-panel |
--wr-dialog-close-radius | var(--wr-border-radius-sm) | .wr-dialog-panel |
--wr-dialog-content-font-size | var(--wr-text-base) | .wr-dialog-panel |
--wr-dialog-content-line-height | var(--wr-text-base-leading) | .wr-dialog-panel |
--wr-dialog-font-family | var(--wr-font-family-base) | .wr-dialog-panel |
--wr-dialog-footer-gap | var(--wr-space-sm) | .wr-dialog-panel |
--wr-dialog-min-width | 18rem | .wr-dialog-panel |
--wr-dialog-padding-x | var(--wr-space-lg) | .wr-dialog-panel |
--wr-dialog-padding-y | var(--wr-space-lg) | .wr-dialog-panel |
--wr-dialog-radius | var(--wr-border-radius-lg) | .wr-dialog-panel |
--wr-dialog-shadow | var(--wr-shadow-modal) | .wr-dialog-panel |
--wr-dialog-title-font-size | var(--wr-text-lg) | .wr-dialog-panel |
--wr-dialog-title-font-weight | var(--wr-font-weight-semibold) | .wr-dialog-panel |
--wr-dialog-title-line-height | var(--wr-text-base-leading) | .wr-dialog-panel |
Dependencies
| Angular CDK | a11y, overlay, portal, scrolling |
|---|---|
| Built on | Overlay |
See also
Component DrawerA side panel that slides in from any edge of the screen.Component PopconfirmAsk "Are you sure?" next to a button before its action runs.Component WindowDraggable, resizable windows that you open from code with WrWindowManager.Component ToastShort notifications that stack in a corner of the screen.Guide OverlayStyle ngwr's floating panels and control where focus goes.