CngxDialog
projects/common/dialog/dialog/dialog.directive.ts
Import#
import { CngxDialog } from '@cngx/common/dialog'
Description#
Signal-driven state machine for native <dialog>.
Wraps the browser's <dialog> element with reactive state, typed results,
deterministic focus management, and full ARIA communication. Supports both
modal and non-modal modes, CSS transition-aware open/close lifecycle, and
ref-counted scroll locking for stacked modals.
The directive implements DialogRef<T> and provides itself via DIALOG_REF,
so child directives (CngxDialogTitle, CngxDialogClose, etc.) can inject
the reference without explicit wiring.
State lifecycle: closed -> opening -> open -> closing -> closed.
CSS classes cngx-dialog--opening, cngx-dialog--open, and
cngx-dialog--closing are applied to the host for transition hooks.
Declarative (template-driven)
<dialog cngxDialog #dlg="cngxDialog">
<h2 cngxDialogTitle>Delete item?</h2>
<p cngxDialogDescription>This cannot be undone.</p>
<button [cngxDialogClose]="false">Cancel</button>
<button [cngxDialogClose]="true">Delete</button>
</dialog>
<button (click)="dlg.open()">Delete</button>
@if (dlg.lifecycle() === 'closed' && dlg.result() === true) {
<p>Item deleted.</p>
}Programmatic (via CngxDialogOpener)
const ref = this.dialogService.open<boolean>(ConfirmDialog, {
data: { message: 'Delete item?' },
});
ref.afterClosed().subscribe(result => {
if (result === true) this.delete();
});Non-modal dialog
<dialog cngxDialog [modal]="false" #tooltip="cngxDialog">
<p>Inline tooltip content</p>
</dialog>Custom focus target
<dialog cngxDialog [autoFocus]="'#name-input'">
<input id="name-input" />
</dialog>Metadata#
Host#
Providers#
DIALOG_REF- useExisting
CngxDialog
Relationships
Depends on1
Index#
Properties
Derived State
HostBindings
Inputs#
'first-focusable' | 'none' | (string & {})Focus strategy applied after the dialog transitions to 'open'.
Only applies to modal dialogs.
'first-focusable'-- focus the first[autofocus]element, or the first focusable element in DOM order (default)'none'-- do not move focus- Any CSS selector string -- focus the first element matching the selector
'first-focusable'Whether clicking the backdrop dismisses the dialog.
Only applies to modal dialogs. When true, a click on the
::backdrop pseudo-element calls dismiss().
trueWhether pressing Escape dismisses the dialog.
Only applies to modal dialogs. The native cancel event is always
prevented; this input controls whether dismiss() is called in response.
trueWhether the dialog is in an error state. Fallback when neither [state]
nor [submitAction] is set.
When true, applies the cngx-dialog--error CSS class on the host.
Use this to communicate form submission failures or other error
conditions visually.
Pair with cngx-form-errors (role="alert") inside the dialog for
WCAG-compliant error announcements.
falseHTMLElement | undefinedFallback element to focus when the original trigger element has been removed from the DOM by the time the dialog closes.
When not set and the trigger is gone, no focus return occurs.
Whether the dialog opens as modal (showModal()) or non-modal (show()).
Modal dialogs block interaction with the rest of the page, acquire a
scroll lock, and participate in the CngxDialogStack for backdrop
management. Non-modal dialogs do not.
trueBind an async state - drives pending (aria-busy, prevents close) and
error from a single source. When set, takes precedence over the
[error] boolean input and [submitAction].
When state.status() is 'pending', the dialog prevents close/dismiss
and applies aria-busy. When state.status() is 'error', the dialog
applies cngx-dialog--error and announces the error via the SR live region.
Instance Properties#
unknownUnique auto-generated ID for this dialog instance. Used for ARIA and stack tracking.
this.idSignal.asReadonly()unknownCurrent lifecycle state of the dialog.
Possible values: 'closed', 'opening', 'open', 'closing'.
this.lifecycleSignal.asReadonly()unknownThe typed result of the dialog.
undefinedbefore close (reset on eachopen()call)'dismissed'when dismissed via Escape or backdrop clickTwhen closed with an explicit value viaclose(value)
this.resultSignal.asReadonly()unknownAsync action to execute when close(value) is called.
Receives the close value as parameter. On success, the dialog auto-closes.
On error, the dialog stays open with cngx-dialog--error and the error
is announced to screen readers.
When set, close(value) no longer closes immediately - it enters a
submitting phase (isPending() = true, aria-busy, close blocked).
The submit lifecycle is exposed via submitState: CngxAsyncState<unknown>.
Ignored when [state] input is also set (external state takes precedence).
input<((value: T) => Promise<unknown> | Observable<unknown>) | undefined>(
undefined,
)CngxAsyncStateAsync state of the submit channel.
Populated when [submitAction] is set. Tracks idle -> pending ->
success/error. When submitAction is not set, remains at 'idle'.
Bind to any state consumer: <cngx-alert [state]="dlg.submitState" />.
buildAsyncStateView<unknown>({
status: this.submitStatusState.asReadonly(),
data: computed(() => undefined),
error: this.submitErrorState.asReadonly(),
})Methods#
close(value: T)Close the dialog with a typed result.
When [submitAction] is set and [state] is not set, executes the action
with value before closing. The dialog enters a submitting phase
(isPending() = true) - on success it auto-closes, on error it stays open.
When [submitAction] is not set (or [state] overrides), closes immediately.
No-op if the dialog is not in the 'open' or 'opening' state, or if
already pending.
T- The typed result to deliver to consumers.
Dismiss the dialog without a typed result.
Sets result to 'dismissed', then initiates the closing transition.
Typically triggered by Escape key or backdrop click, but can be called
directly. No-op if the dialog is not in the 'open' or 'opening' state.
HostBindings#
| Binding | Expression |
|---|---|
[class.cngx-dialog--opening] | isOpening() |
[class.cngx-dialog--open] | isOpen() |
[class.cngx-dialog--closing] | isClosing() |
[class.cngx-dialog--modal] | modal() |
[attr.aria-modal] | ariaModal() |
[attr.aria-labelledby] | ariaLabelledBy() |
[attr.aria-describedby] | ariaDescribedBy() |
[class.cngx-dialog--pending] | isPending() |
[attr.aria-busy] | isPending() || null |
[class.cngx-dialog--error] | effectiveError() |
[style.--cngx-dialog-backdrop-opacity] | backdropOpacity() |
HostListeners#
| Event | Handler |
|---|---|
(cancel) | cancel() |
(click) | click() |