Skip to main content
cngx-src documentation

CngxDialog

DirectivePrimaryv0.1.0WCAG AA

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>

https://cngxjs.github.io/cngx/examples/#/common/dialog/alert-dialog https://cngxjs.github.io/cngx/examples/#/common/dialog/bottom-sheet https://cngxjs.github.io/cngx/examples/#/common/dialog/cngxdialogopener-programmatic https://cngxjs.github.io/cngx/examples/#/common/dialog/draggable-dialog https://cngxjs.github.io/cngx/examples/#/common/dialog/fully-declarative https://cngxjs.github.io/cngx/examples/#/common/dialog/grid-snap-live-vs-release https://cngxjs.github.io/cngx/examples/#/common/dialog/nested-dialogs-cngxdialogstack https://cngxjs.github.io/cngx/examples/#/common/dialog/non-modal-panel https://cngxjs.github.io/cngx/examples/#/common/dialog/programmatic-control https://cngxjs.github.io/cngx/examples/#/common/dialog/template-directives

Metadata#

Host#

Providers#

DIALOG_REF
useExisting CngxDialog

Relationships

Index#

Inputs#

autoFocus#'first-focusable' | 'none' | (string & {})
input()

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
default 'first-focusable'
closeOnBackdropClick#
input()

Whether clicking the backdrop dismisses the dialog.

Only applies to modal dialogs. When true, a click on the ::backdrop pseudo-element calls dismiss().

default true
closeOnEscape#
input()

Whether 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.

default true
error#
input()

Whether 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.

default false
focusFallback#HTMLElement | undefined
input()

Fallback 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.

Bind 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#

id#unknown
Readonly

Unique auto-generated ID for this dialog instance. Used for ARIA and stack tracking.

this.idSignal.asReadonly()
lifecycle#unknown
Readonly

Current lifecycle state of the dialog.

Possible values: 'closed', 'opening', 'open', 'closing'.

this.lifecycleSignal.asReadonly()
result#unknown
Readonly

The typed result of the dialog.

  • undefined before close (reset on each open() call)
  • 'dismissed' when dismissed via Escape or backdrop click
  • T when closed with an explicit value via close(value)
this.resultSignal.asReadonly()
submitAction#unknown
Readonly

Async 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,
)
submitState#CngxAsyncState
Readonly

Async 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#void
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.

@paramvalueT
  • The typed result to deliver to consumers.
dismiss#void

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.

handleCancel#void
Protected
handleCancel(event: Event)
@parameventEvent
handleClick#void
Protected
handleClick(event: MouseEvent)
@parameventMouseEvent
open#void

Open the dialog.

Resets the result from any previous open/close cycle, stores the currently focused element for focus return, and calls the native showModal() or show() depending on the modal input.

No-op if the dialog is already open or in a transition state.

HostBindings#

BindingExpression
[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#

EventHandler
(cancel)cancel()
(click)click()