Alert Dialog
A modal that asks for an explicit choice.
import { โฆ } from '@inlaylab/ivorie/alert-dialog'Last close: none
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvButton } from '@inlaylab/ivorie/button';
import { AlertDialogCloseEvent, IV_ALERT_DIALOG_IMPORTS } from '@inlaylab/ivorie/alert-dialog';
@Component({
selector: 'demo-alert-dialog',
imports: [IV_ALERT_DIALOG_IMPORTS, IvButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col items-center gap-2">
<button ivButton variant="outline" [ivAlertDialogTrigger]="confirmTpl" (closed)="last.set($event)">
Delete account
</button>
<p class="text-sm text-muted-foreground">Last close: {{ last()?.reason ?? 'none' }}</p>
</div>
<ng-template #confirmTpl>
<iv-alert-dialog-header>
<iv-alert-dialog-title>Delete account?</iv-alert-dialog-title>
<iv-alert-dialog-description>
This action cannot be undone. All your data will be permanently removed.
</iv-alert-dialog-description>
</iv-alert-dialog-header>
<iv-alert-dialog-footer>
<button ivAlertDialogCancel>Cancel</button>
<button ivAlertDialogAction variant="destructive">Delete</button>
</iv-alert-dialog-footer>
</ng-template>
`,
})
export default class AlertDialogDemo {
protected readonly last = signal<AlertDialogCloseEvent | null>(null);
}
Installation
npx ivorie add alert-dialogimport { โฆ } from '@inlaylab/ivorie/alert-dialog'A confirm dialog on CDK Overlay. A backdrop click never closes it. The user picks Cancel or Action.
Usage
import { IvAlertDialog, IV_ALERT_DIALOG_IMPORTS } from '@inlaylab/ivorie/alert-dialog';<button ivButton [ivAlertDialogTrigger]="confirmTpl" (closed)="onClosed($event)">
Delete account
</button>
<ng-template #confirmTpl>
<iv-alert-dialog-header>
<iv-alert-dialog-title>Delete account?</iv-alert-dialog-title>
<iv-alert-dialog-description>This action cannot be undone.</iv-alert-dialog-description>
</iv-alert-dialog-header>
<iv-alert-dialog-footer>
<button ivAlertDialogCancel>Cancel</button>
<button ivAlertDialogAction variant="destructive">Delete</button>
</iv-alert-dialog-footer>
</ng-template>private alertDialog = inject(IvAlertDialog);
readonly confirmTpl = viewChild.required<TemplateRef<unknown>>('confirmTpl');
deleteUser() {
const ref = this.alertDialog.open(this.confirmTpl(), { size: 'sm' });
ref.afterClosed().subscribe(({ reason }) => {
if (reason === 'action') this.doDelete();
});
}Examples
Service
import { ChangeDetectionStrategy, Component, TemplateRef, inject, signal, viewChild } from '@angular/core';
import { IvButton } from '@inlaylab/ivorie/button';
import { IV_ALERT_DIALOG_IMPORTS, IvAlertDialog } from '@inlaylab/ivorie/alert-dialog';
@Component({
selector: 'demo-alert-dialog-service',
imports: [IV_ALERT_DIALOG_IMPORTS, IvButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col items-center gap-2">
<button ivButton variant="outline" (click)="remove()">Remove user</button>
@if (removed()) {
<p class="text-sm text-muted-foreground">User removed.</p>
}
</div>
<ng-template #confirmTpl>
<iv-alert-dialog-header>
<iv-alert-dialog-title>Remove user?</iv-alert-dialog-title>
<iv-alert-dialog-description>They lose access right away.</iv-alert-dialog-description>
</iv-alert-dialog-header>
<iv-alert-dialog-footer>
<button ivAlertDialogCancel>Cancel</button>
<button ivAlertDialogAction variant="destructive">Remove</button>
</iv-alert-dialog-footer>
</ng-template>
`,
})
export default class AlertDialogServiceDemo {
private readonly alertDialog = inject(IvAlertDialog);
private readonly confirmTpl = viewChild.required<TemplateRef<unknown>>('confirmTpl');
protected readonly removed = signal(false);
remove() {
this.alertDialog
.open(this.confirmTpl())
.afterClosed()
.subscribe(({ reason }) => this.removed.set(reason === 'action'));
}
}
Media icon
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { LucideTrash2 } from '@lucide/angular';
import { IvButton } from '@inlaylab/ivorie/button';
import { IV_ALERT_DIALOG_IMPORTS } from '@inlaylab/ivorie/alert-dialog';
@Component({
selector: 'demo-alert-dialog-media',
imports: [IV_ALERT_DIALOG_IMPORTS, IvButton, LucideTrash2],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<button ivButton variant="outline" [ivAlertDialogTrigger]="tpl">Delete account</button>
<ng-template #tpl>
<iv-alert-dialog-header>
<iv-alert-dialog-media>
<svg lucideTrash2></svg>
</iv-alert-dialog-media>
<iv-alert-dialog-title>Delete account?</iv-alert-dialog-title>
<iv-alert-dialog-description>This cannot be undone.</iv-alert-dialog-description>
</iv-alert-dialog-header>
<iv-alert-dialog-footer>
<button ivAlertDialogCancel>Cancel</button>
<button ivAlertDialogAction variant="destructive">Delete</button>
</iv-alert-dialog-footer>
</ng-template>
`,
})
export default class AlertDialogMediaDemo {}
Result from action
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvButton } from '@inlaylab/ivorie/button';
import { AlertDialogCloseEvent, IV_ALERT_DIALOG_IMPORTS } from '@inlaylab/ivorie/alert-dialog';
@Component({
selector: 'demo-alert-dialog-result',
imports: [IV_ALERT_DIALOG_IMPORTS, IvButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col items-center gap-2">
<button ivButton variant="outline" [ivAlertDialogTrigger]="tpl" (closed)="onClosed($event)">
Archive project
</button>
@if (archivedId()) {
<p class="text-sm text-muted-foreground">Archived project {{ archivedId() }}</p>
}
</div>
<ng-template #tpl>
<iv-alert-dialog-header>
<iv-alert-dialog-title>Archive project 42?</iv-alert-dialog-title>
<iv-alert-dialog-description>You can restore it later.</iv-alert-dialog-description>
</iv-alert-dialog-header>
<iv-alert-dialog-footer>
<button ivAlertDialogCancel>Cancel</button>
<button [ivAlertDialogAction]="42">Archive</button>
</iv-alert-dialog-footer>
</ng-template>
`,
})
export default class AlertDialogResultDemo {
protected readonly archivedId = signal<number | null>(null);
onClosed({ reason, result }: AlertDialogCloseEvent) {
if (reason === 'action') this.archivedId.set(result as number);
}
}
Small size
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IvButton } from '@inlaylab/ivorie/button';
import { IV_ALERT_DIALOG_IMPORTS } from '@inlaylab/ivorie/alert-dialog';
@Component({
selector: 'demo-alert-dialog-small',
imports: [IV_ALERT_DIALOG_IMPORTS, IvButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<button ivButton variant="outline" [ivAlertDialogTrigger]="tpl" [ivAlertDialogConfig]="{ size: 'sm' }">
Sign out
</button>
<ng-template #tpl>
<iv-alert-dialog-header>
<iv-alert-dialog-title>Sign out?</iv-alert-dialog-title>
<iv-alert-dialog-description>You will need to sign in again.</iv-alert-dialog-description>
</iv-alert-dialog-header>
<iv-alert-dialog-footer>
<button ivAlertDialogCancel>Stay</button>
<button ivAlertDialogAction>Sign out</button>
</iv-alert-dialog-footer>
</ng-template>
`,
})
export default class AlertDialogSmallDemo {}
API
Every part also accepts class.
IvAlertDialog
Service, providedIn: 'root'.
| Method | Signature | Description |
|---|---|---|
open |
<T, R, D>(content: Type<T> | TemplateRef<unknown>, config?: AlertDialogConfig<D>) => IvAlertDialogRef<T, R> |
Opens an alert dialog. |
A template gets $implicit (the ref), data and dialogRef in its context. A component gets IvAlertDialogRef and IV_ALERT_DIALOG_DATA by injection.
IvAlertDialogRef
| Member | Signature | Description |
|---|---|---|
close |
(result?: R, reason?: AlertDialogCloseReason) => void |
Closes the dialog. reason defaults to 'programmatic'. Only the first close counts. |
afterClosed |
() => Observable<AlertDialogCloseEvent<R>> |
Emits once, then completes. |
closed |
Signal<AlertDialogCloseEvent<R> | undefined> |
undefined while open, then the close event. |
componentInstance |
T | undefined |
The live instance when opened with a component. |
AlertDialogConfig
| Name | Type | Default | Description |
|---|---|---|---|
data |
D |
null |
Injected as IV_ALERT_DIALOG_DATA. Also in the template context as data. |
size |
'default' | 'sm' |
'default' |
Width and footer layout. sm makes the footer a two-column grid. |
disableEscapeClose |
boolean |
false |
Escape does not close. |
panelClass |
string | string[] |
none | Extra classes on the overlay panel. |
AlertDialogCloseEvent
{ reason: AlertDialogCloseReason; result?: R }.
| Reason | Meaning |
|---|---|
'action' |
An ivAlertDialogAction button. |
'cancel' |
An ivAlertDialogCancel button. |
'escape' |
Escape key. |
'programmatic' |
A close() call with no reason. |
'navigation' |
The overlay was disposed from outside, for example by a route change. |
IV_ALERT_DIALOG_DATA
Injection token for the data passed to open.
ivAlertDialogTrigger
Opens an alert dialog on click.
| Name | Type | Default | Description |
|---|---|---|---|
ivAlertDialogTrigger |
Type<T> | TemplateRef<unknown> |
required | What to open (class field content). |
ivAlertDialogConfig |
AlertDialogConfig<D> |
{} |
Config for open. |
Outputs:
| Name | Payload | Description |
|---|---|---|
opened |
IvAlertDialogRef<T, R> |
Fires when the dialog opens. |
closed |
AlertDialogCloseEvent<R> |
Fires when the dialog closes. |
button[ivAlertDialogAction]
Closes with reason 'action'. Styled as a button, so do not add ivButton.
| Name | Type | Default | Description |
|---|---|---|---|
ivAlertDialogAction |
R |
undefined |
Result passed to close (class field result). |
variant |
ButtonVariants['variant'] |
'default' |
Button variant. |
size |
ButtonVariants['size'] |
'default' |
Button size. |
button[ivAlertDialogCancel]
Closes with reason 'cancel'. Styled as a button, so do not add ivButton.
| Name | Type | Default | Description |
|---|---|---|---|
ivAlertDialogCancel |
R |
undefined |
Result passed to close (class field result). |
variant |
ButtonVariants['variant'] |
'outline' |
Button variant. |
size |
ButtonVariants['size'] |
'default' |
Button size. |
iv-alert-dialog-header, iv-alert-dialog-footer, iv-alert-dialog-media, iv-alert-dialog-title, iv-alert-dialog-description
Layout parts. No inputs. Put iv-alert-dialog-media first in the header: the grid relies on source order.
Accessibility
- The panel has
role="alertdialog"andaria-modal="true". - It is labelled by
iv-alert-dialog-titleand described byiv-alert-dialog-description. - Focus is trapped inside the panel. Cancel gets initial focus: the least destructive choice.
- On close, focus returns to the element that was focused before open. It is not moved if focus is already somewhere else outside the dialog. This runs before
afterClosedemits. - Escape closes the dialog, unless
disableEscapeCloseis set. Only the topmost overlay closes. - Body scroll is blocked while open.
Notes
- Action and Cancel inject
IvAlertDialogRef, so use them inside a template or component opened by the service. sizeis set in theopenconfig only. Layout children follow it.- For a strict flow, set
disableEscapeClose: true.