{"id":51820531,"url":"https://github.com/kazepis/ngx-modalieur","last_synced_at":"2026-07-24T14:00:38.249Z","repository":{"id":367083235,"uuid":"1278144955","full_name":"kazepis/ngx-modalieur","owner":"kazepis","description":"Bootstrap-styled modals over Angular CDK Dialog. Open a modal, subscribe to the result — no modal IDs or global result buses. ","archived":false,"fork":false,"pushed_at":"2026-06-24T12:53:57.000Z","size":467,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-24T14:18:32.083Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kazepis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-23T14:00:48.000Z","updated_at":"2026-06-24T12:54:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kazepis/ngx-modalieur","commit_stats":null,"previous_names":["kazepis/ngx-modalieur"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kazepis/ngx-modalieur","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kazepis%2Fngx-modalieur","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kazepis%2Fngx-modalieur/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kazepis%2Fngx-modalieur/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kazepis%2Fngx-modalieur/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kazepis","download_url":"https://codeload.github.com/kazepis/ngx-modalieur/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kazepis%2Fngx-modalieur/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35844578,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-24T02:00:07.870Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-22T05:00:25.537Z","updated_at":"2026-07-24T14:00:38.207Z","avatar_url":"https://github.com/kazepis.png","language":"TypeScript","funding_links":[],"categories":["Third Party Components"],"sub_categories":["Modals"],"readme":"# ngx-modalieur\n\n**Reactive Bootstrap modals for Angular — a thin, typed layer on [CDK Dialog](https://material.angular.dev/cdk/dialog/overview).**\n\n[![npm version](https://img.shields.io/npm/v/ngx-modalieur)](https://www.npmjs.com/package/ngx-modalieur)\n[![npm downloads](https://img.shields.io/npm/dw/ngx-modalieur)](https://www.npmjs.com/package/ngx-modalieur)\n![Angular](https://img.shields.io/badge/Angular-22-red)\n![License](https://img.shields.io/badge/License-MIT-blue)\n[![Live demo](https://img.shields.io/badge/demo-live-brightgreen)](https://kazepis.github.io/ngx-modalieur/)\n\n```bash\nnpm install ngx-modalieur @angular/cdk bootstrap\n```\n\n\u003e **Open a modal, `subscribe` to the result.** No modal ids, no global result bus, no boilerplate.\n\n**[Try the live demo](https://kazepis.github.io/ngx-modalieur/)** — interactive examples and a config playground in your browser.\n\n## Table of contents\n\n- [What is this?](#what-is-this)\n- [Why use it?](#why-use-it)\n- [When to use something else](#when-to-use-something-else)\n- [Requirements](#requirements)\n- [Setup](#setup)\n- [Quick start (60 seconds)](#quick-start-60-seconds)\n- [Core concepts](#core-concepts)\n  - [Two layers](#two-layers)\n  - [Result flow](#result-flow)\n  - [Config layering](#config-layering)\n- [Usage guide](#usage-guide)\n  - [Message boxes](#message-boxes)\n  - [Custom modal components](#custom-modal-components)\n  - [Typing modals](#typing-modals)\n  - [Passing data in and out](#passing-data-in-and-out)\n  - [Configuration](#configuration)\n  - [Reactive patterns](#reactive-patterns)\n  - [Auto-close with observables](#auto-close-with-observables)\n  - [Programmatic control](#programmatic-control)\n  - [Custom layouts](#custom-layouts)\n  - [Lazy loading](#lazy-loading)\n- [API reference](#api-reference)\n- [Testing](#testing)\n- [License](#license)\n\n## What is this?\n\nngx-modalieur wraps **Angular CDK `Dialog`** with a **Bootstrap 5.3** shell, a standardized **`ModalOutcome`** result model, and convenience APIs for confirm / alert dialogs.\n\nIt is **not** a replacement for CDK Dialog. Focus trapping, overlay positioning, backdrop, and Escape handling still come from CDK. This library adds the Bootstrap markup, bridging CSS, reactive close semantics, and message-box shortcuts so you do not rebuild that glue in every app.\n\nNew to Angular modals? Start with [Quick start](#quick-start-60-seconds) — you will have a working confirm dialog in under a minute.\n\n## Why use it?\n\n|                       | Raw CDK Dialog                            | ngx-modalieur                                     |\n| --------------------- | ----------------------------------------- | ------------------------------------------------- |\n| Styling               | Bring your own container and CSS          | Bootstrap `.modal-dialog` shell + bridging styles |\n| Close result          | `dialogRef.close(value)` — shape is yours | Standardized `{ result: ModalResult; data? }`     |\n| Message boxes         | Build yourself                            | `confirm()`, `alert()`, `messageBox()`            |\n| App-wide defaults     | DIY injection token                       | `provideModalieur({ … })`                         |\n| Auto-close on streams | Wire `takeUntil` + `close()` yourself     | `showUntil()` / `showUntilCondition()`            |\n\n**Subscribe, don't wire.** `show()` returns `Observable\u003cModalOutcome\u003e`. Open a modal, react in one `subscribe` or `pipe` — no modal ids, no global result bus, no `setResult(id, …)` from inside the component.\n\n**Bootstrap without glue.** `ModalieurService` automatically applies `BootstrapDialogContainer`, which wraps your component in `.modal \u003e .modal-dialog \u003e .modal-content`. You only render header, body, and footer.\n\n**Built-in confirm / alert.** One-liners for the dialogs every app reimplements.\n\n**Observable-driven auto-close.** Keep a modal open until a timer fires, a hub event arrives, or an async job completes — with a dedicated `ModalResult.AutoClose`.\n\n## When to use something else\n\n- **Angular Material apps** — use [`MatDialog`](https://material.angular.dev/components/dialog/overview); it is integrated with Material theming.\n- **Non-Bootstrap design systems** — use CDK Dialog directly with your own container.\n- **A single one-off overlay** — CDK alone is enough; this library shines when modals are a recurring pattern.\n- **Angular \u003c 22** — not supported. Peer dependencies require `@angular/core`, `@angular/common`, and `@angular/cdk` `^22.0.0`.\n\n## Requirements\n\n| Package                            | Version   | Required                                         |\n| ---------------------------------- | --------- | ------------------------------------------------ |\n| `@angular/core`, `@angular/common` | `^22.0.0` | Yes                                              |\n| `@angular/cdk`                     | `^22.0.0` | Yes                                              |\n| `bootstrap`                        | `^5.3.0`  | Optional (needed for the default Bootstrap look) |\n\n## Setup\n\n**1. Install**\n\n```bash\nnpm install ngx-modalieur @angular/cdk bootstrap\n```\n\n**2. Add global styles** (e.g. in `angular.json` → `projects.[app].architect.build.options.styles`):\n\n```json\n\"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n\"node_modules/@angular/cdk/overlay-prebuilt.css\",\n\"node_modules/ngx-modalieur/styles/ngx-modalieur.css\"\n```\n\nThe library stylesheet bridges CDK overlay behavior with Bootstrap modal appearance (backdrop darkness, scrollable body layout, enter animation).\n\n**3. Register app-wide defaults** (optional but recommended):\n\n```ts\n// app.config.ts\nimport { ApplicationConfig } from '@angular/core';\nimport { provideModalieur } from 'ngx-modalieur';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    // Override only what you need; unset fields keep MODALIEUR_DEFAULTS.\n    provideModalieur({ dismissible: false, size: 'lg' })\n  ]\n};\n```\n\nCalling `provideModalieur()` with no arguments registers built-in defaults. Omitting `provideModalieur()` entirely also works — the service falls back to `MODALIEUR_DEFAULTS` internally.\n\n## Quick start (60 seconds)\n\nThe fastest possible modal — no custom component needed:\n\n```ts\nimport { inject } from '@angular/core';\nimport { ModalieurService, ModalResult } from 'ngx-modalieur';\n\nexport class MyComponent {\n  private readonly modalieur = inject(ModalieurService);\n\n  deleteItem(): void {\n    this.modalieur.confirm('Delete item?', 'This cannot be undone.').subscribe(result =\u003e {\n      if (result === ModalResult.Yes) {\n        // user confirmed — do the work\n      }\n    });\n  }\n}\n```\n\nNeed your own content? Define a modal component by extending `ModalContent`, render the Bootstrap inner sections, and close via the built-in helpers:\n\n```ts\nimport { Component } from '@angular/core';\nimport { ModalContent } from 'ngx-modalieur';\n\n@Component({\n  template: `\n    \u003cdiv class=\"modal-header\"\u003e\n      \u003ch5 class=\"modal-title\"\u003e{{ data.title }}\u003c/h5\u003e\n      \u003cbutton type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"cancel()\"\u003e\u003c/button\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"modal-body\"\u003e{{ data.message }}\u003c/div\u003e\n    \u003cdiv class=\"modal-footer\"\u003e\n      \u003cbutton type=\"button\" class=\"btn btn-secondary\" (click)=\"no()\"\u003eNo\u003c/button\u003e\n      \u003cbutton type=\"button\" class=\"btn btn-primary\" (click)=\"yes()\"\u003eYes\u003c/button\u003e\n    \u003c/div\u003e\n  `\n})\nexport class ConfirmModalComponent extends ModalContent\u003c{ title: string; message: string }, never\u003e {}\n```\n\nThen open it and react to the outcome:\n\n```ts\nthis.modalieur\n  .show(ConfirmModalComponent, {\n    data: { title: 'Confirm', message: 'Are you sure?' }\n  })\n  .subscribe(outcome =\u003e {\n    if (outcome.result === ModalResult.Yes) {\n      // user clicked Yes\n    }\n  });\n```\n\nInput is available inside the modal as `this.data` (typed from the first generic). `config.data` is **required** at the call site when the modal declares input.\n\n## Core concepts\n\n### Two layers\n\n```mermaid\nflowchart LR\n  Caller[Caller subscribes to show]\n  Service[ModalieurService]\n  Shell[BootstrapDialogContainer]\n  Content[Your ModalContent component]\n  Caller --\u003e|show| Service\n  Service --\u003e Shell\n  Shell --\u003e Content\n  Content --\u003e|\"yes / cancel / respondWithData\"| Outcome[ModalOutcome emitted once]\n  Outcome --\u003e Caller\n```\n\n1. **Your component** (`extends ModalContent`) — renders `.modal-header`, `.modal-body`, `.modal-footer` and closes via `yes()`, `cancel()`, `respondWithData()`, etc.\n2. **Dialog shell** (`BootstrapDialogContainer`) — applied automatically unless `unstyled: true`. Wraps your component in Bootstrap's outer modal markup.\n\nYou do **not** extend `BootstrapDialogContainer` for normal modals. It is exported for advanced CDK container customization only. For fully custom layouts (viewport-filling overlays with your own CSS), pass `unstyled: true` and style the component yourself.\n\n### Result flow\n\nEvery close produces a **`ModalOutcome\u003cT\u003e`**:\n\n```ts\ninterface ModalOutcome\u003cTData = unknown\u003e {\n  result: ModalResult;\n  data?: TData;\n}\n```\n\nThe observable emits **once**, then completes. Backdrop click and Escape map to `ModalResult.Cancel` when `dismissible` is `true`.\n\nWhen you call `show(MyModal, …)`, `T` is inferred from the component's `ModalContent\u003cTDataIn, TDataOut\u003e` declaration (`TDataOut`). `confirm()`, `alert()`, and `messageBox()` unwrap this to `Observable\u003cModalResult\u003e` for convenience.\n\n### Config layering\n\nPer-call config is merged in this order (later wins):\n\n```\nMODALIEUR_DEFAULTS  →  provideModalieur(...)  →  per-call config\n```\n\nBuilt-in defaults (`MODALIEUR_DEFAULTS`):\n\n| Option        | Default |\n| ------------- | ------- |\n| `backdrop`    | `true`  |\n| `centered`    | `true`  |\n| `dismissible` | `true`  |\n| `scrollable`  | `false` |\n| `unstyled`    | `false` |\n\n## Usage guide\n\n### Message boxes\n\nAll message-box APIs open the same built-in component (`MessageBoxDialog`). Pick the API by what you need back and how much wiring you want the library to do:\n\n| API                         | Prefer when                                                        | `subscribe` receives | A11y (`aria-labelledby` / `aria-describedby`) |\n| --------------------------- | ------------------------------------------------------------------ | -------------------- | --------------------------------------------- |\n| `confirm()` / `alert()`     | Yes/No or OK only                                                  | `ModalResult`        | Auto-wired                                    |\n| `messageBox({ … })`         | Custom button set                                                  | `ModalResult`        | Auto-wired                                    |\n| `show(MessageBoxDialog, …)` | You want `ModalOutcome`, or full control over `ModalConfig` / aria | `ModalOutcome`       | **You** must set aria (see below)             |\n\n#### Shorthand (recommended for most cases)\n\n`confirm()`, `alert()`, and `messageBox()` are thin wrappers around `show(MessageBoxDialog, …)`. They unwrap the result to `ModalResult` and point the dialog at the title and body element ids for screen readers.\n\n```ts\nthis.modalieur.confirm('Delete item?', 'This cannot be undone.').subscribe(result =\u003e {\n  if (result === ModalResult.Yes) {\n    this.deleteItem();\n  }\n});\n\nthis.modalieur.alert('Saved', 'Your changes were saved.', { size: 'sm' }).subscribe();\n\nthis.modalieur\n  .messageBox({\n    title: 'Retry?',\n    message: 'Could not reach the server.',\n    buttons: MessageBoxButtons.RetryCancel\n  })\n  .subscribe(result =\u003e {\n    // ModalResult.Retry | ModalResult.Cancel\n  });\n```\n\n`confirm()` and `alert()` do not force a size — pass `{ size: 'sm' }` (or any `ModalConfig` field) when you want a compact dialog.\n\nAvailable button sets (`MessageBoxButtons` enum): `OK`, `OKCancel`, `YesNo`, `YesNoCancel`, `AbortRetryIgnore`, `RetryCancel`.\n\n#### Low-level: `show(MessageBoxDialog, …)`\n\nUse this when you need the full `ModalOutcome` shape (`{ result, data? }`) for consistency with other `show()` calls, or when you want to pass `ModalConfig` without going through `messageBox()`.\n\n**Accessibility:** `messageBox()` / `confirm()` / `alert()` automatically set `ariaLabelledBy` and `ariaDescribedBy` to match the ids on the message-box title and body (`mdlr-message-box-title`, `mdlr-message-box-body`). If you call `show(MessageBoxDialog, …)` directly, pass those ids (or import the constants) so CDK Dialog can label the overlay correctly:\n\n```ts\nimport { MESSAGE_BOX_BODY_ID, MESSAGE_BOX_TITLE_ID, MessageBoxDialog, MessageBoxButtons } from 'ngx-modalieur';\n\nthis.modalieur\n  .show(MessageBoxDialog, {\n    ariaLabelledBy: MESSAGE_BOX_TITLE_ID,\n    ariaDescribedBy: MESSAGE_BOX_BODY_ID,\n    data: { title: 'Delete?', message: 'Cannot be undone.', buttons: MessageBoxButtons.YesNo }\n  })\n  .subscribe(outcome =\u003e {\n    // outcome.result === ModalResult.Yes | ModalResult.No | ModalResult.Cancel\n  });\n```\n\n#### Custom markup (content projection)\n\n```ts\n@Component({\n  imports: [MessageBoxDialog],\n  template: `\n    \u003cmdlr-message-box\u003e\n      \u003cdiv mbHeader\u003eCustom header\u003c/div\u003e\n      \u003cdiv mbBody\u003eCustom body\u003c/div\u003e\n      \u003cdiv mbFooter class=\"d-flex gap-2\"\u003e\n        \u003cbutton type=\"button\" class=\"btn btn-secondary\" (click)=\"cancel()\"\u003eDismiss\u003c/button\u003e\n        \u003cbutton type=\"button\" class=\"btn btn-primary\" (click)=\"ok()\"\u003eGot it\u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/mdlr-message-box\u003e\n  `\n})\nexport class MyMessageBox extends ModalContent {}\n```\n\n### Custom modal components\n\nExtend `ModalContent` and use the protected close helpers:\n\n| Method                  | `ModalResult` |\n| ----------------------- | ------------- |\n| `yes(data?)`            | `Yes`         |\n| `no(data?)`             | `No`          |\n| `ok(data?)`             | `Ok`          |\n| `cancel(data?)`         | `Cancel`      |\n| `abort(data?)`          | `Abort`       |\n| `retry(data?)`          | `Retry`       |\n| `ignore(data?)`         | `Ignore`      |\n| `respondWithData(data)` | `Data`        |\n| `close(result?, data?)` | any           |\n\nThe modal component does not inject a global modal service. Closing the dialog **is** emitting the result.\n\n### Typing modals\n\nEvery modal extends `ModalContent\u003cTDataIn = void, TDataOut = never\u003e`. The component is the single source of truth — `show()` infers input and output types from it.\n\n| Shape               | Declaration               | `config.data` | `outcome.data`             |\n| ------------------- | ------------------------- | ------------- | -------------------------- |\n| No input, no output | `ModalContent` (defaults) | optional      | never (result only)        |\n| Input only          | `ModalContent\u003cIn, never\u003e` | **required**  | never                      |\n| Output only         | `ModalContent\u003cvoid, Out\u003e` | optional      | typed (optional to return) |\n| Both                | `ModalContent\u003cIn, Out\u003e`   | **required**  | typed (optional to return) |\n\n- **`void`** — no meaningful input; `this.data` exists but is unusable.\n- **`never`** — cannot return output data; `respondWithData` is uncallable.\n- **A concrete `TDataOut`** — returning data is **optional**: `close()` / `yes()` accept `data?`, so the same modal can close with or without a payload. `respondWithData` is the explicit always-with-data path.\n\nInside the modal, input is available as **`this.data`** (injected by the base class). Do not inject `MODAL_DATA` manually. `ModalDataIn\u003cC\u003e` and `ModalDataOut\u003cC\u003e` extract types from a component class for advanced/generic callers.\n\n### Passing data in and out\n\n- **Input** — declare `TDataIn` on `ModalContent`; pass via `config.data` (required when `TDataIn` is not `void`); read as `this.data` inside the modal.\n- **Output** — declare `TDataOut` on `ModalContent`; returned as `outcome.data` when a close helper includes a payload.\n\n```ts\nclass EditModal extends ModalContent\u003c{ id: number }, { saved: boolean }\u003e {\n  protected save = () =\u003e this.respondWithData({ saved: true });\n}\n\nthis.modalieur.show(EditModal, { data: { id: 7 } }).subscribe(outcome =\u003e {\n  if (outcome.result === ModalResult.Data \u0026\u0026 outcome.data) {\n    console.log(outcome.data.saved); // true after save()\n  }\n});\n```\n\n### Configuration\n\nAll options live on `ModalConfig` and can be set app-wide (`provideModalieur`) or per call:\n\n| Option            | Description                                                                 | Default                                     |\n| ----------------- | --------------------------------------------------------------------------- | ------------------------------------------- |\n| `data`            | Injected as `this.data`; required at call site when `TDataIn` is not `void` | —                                           |\n| `size`            | `'sm' \\| 'md' \\| 'lg' \\| 'xl' \\| 'fullscreen'`                              | Bootstrap medium (`md` adds no extra class) |\n| `centered`        | `.modal-dialog-centered`                                                    | `true`                                      |\n| `scrollable`      | `.modal-dialog-scrollable`                                                  | `false`                                     |\n| `dismissible`     | Backdrop click / Escape closes → `Cancel`                                   | `true`                                      |\n| `backdrop`        | Render CDK backdrop                                                         | `true`                                      |\n| `unstyled`        | Skip Bootstrap shell; component owns layout                                 | `false`                                     |\n| `ariaLabel`       | CDK `ariaLabel`                                                             | —                                           |\n| `ariaLabelledBy`  | CDK `ariaLabelledBy`                                                        | —                                           |\n| `ariaDescribedBy` | CDK `ariaDescribedBy`                                                       | —                                           |\n\nTry every combination live in the [playground](https://kazepis.github.io/ngx-modalieur/#playground).\n\nNon-dismissible modals with no backdrop (common in kiosk / operator UIs):\n\n```ts\nprovideModalieur({ dismissible: false, backdrop: false, centered: true });\n```\n\nScrollable body with long content:\n\n```ts\nthis.modalieur.show(ConfirmModalComponent, {\n  scrollable: true,\n  data: { title: 'Terms', message: longText }\n});\n```\n\n### Reactive patterns\n\nOpening a modal returns an `Observable` — compose with the rest of your RxJS pipelines:\n\n```ts\nimport { filter, switchMap } from 'rxjs/operators';\n\nthis.modalieur\n  .confirm('Delete item?', 'This cannot be undone.')\n  .pipe(\n    filter(result =\u003e result === ModalResult.Yes),\n    switchMap(() =\u003e this.api.deleteItem(id))\n  )\n  .subscribe();\n```\n\n**Compared to imperative modal stacks** many codebases inherit:\n\n```ts\n// Before: ref + global bus + setResult inside the component\nconst ref = modalService.showAndReturnRef(MyModal);\nmodalService.getModalResult(ref).subscribe(/* ... */);\n// inside modal: modalService.setResult(ref.id, ResultType.Yes);\n\n// After: one subscribe, close helpers inside the component\nthis.modalieur.show(MyModal, { data }).subscribe(outcome =\u003e {\n  if (outcome.result === ModalResult.Yes) this.save();\n});\n```\n\n| API                                      | Emits                           | When                                     |\n| ---------------------------------------- | ------------------------------- | ---------------------------------------- |\n| `show(…)`                                | `ModalOutcome\u003cT\u003e`               | User closes or dismisses                 |\n| `confirm()` / `alert()` / `messageBox()` | `ModalResult`                   | Button click or dismiss                  |\n| `showUntil(…)` / `showUntilCondition(…)` | `ModalOutcome` with `AutoClose` | User action **or** observable fires      |\n| `showAndReturnRef(…).closed$`            | `ModalOutcome\u003cT\u003e`               | Same as `show`, plus you hold `ModalRef` |\n\n### Auto-close with observables\n\nKeep a modal open until an external signal fires, then close with `ModalResult.AutoClose`.\n\n|                    | `showUntil`                | `showUntilCondition`                           |\n| ------------------ | -------------------------- | ---------------------------------------------- |\n| Closes when        | First emission (any value) | First **truthy** emission                      |\n| `false`, `0`, `''` | **Closes**                 | Ignored — modal stays open                     |\n| Typical use        | Timers, one-shot events    | Readiness signals (`loaded$`, `saveComplete$`) |\n\n```ts\nimport { timer } from 'rxjs';\nimport { filter, map, take } from 'rxjs/operators';\n\n// Close after 4 seconds regardless of emission value\nthis.modalieur\n  .showUntil(WaitingModalComponent, timer(4000).pipe(map(() =\u003e false)), {\n    data: { title: 'Loading…', message: 'Please wait.' }\n  })\n  .subscribe(outcome =\u003e {\n    // outcome.result === ModalResult.AutoClose\n  });\n\n// Close when status becomes 'done'\nconst ready$ = this.pollStatus().pipe(\n  map(s =\u003e s === 'done'),\n  filter(Boolean),\n  take(1)\n);\nthis.modalieur\n  .showUntilCondition(WaitingModalComponent, ready$, {\n    data: { title: 'Loading…', message: 'Please wait.' }\n  })\n  .subscribe();\n```\n\nThe user can still close early via buttons or dismissal — `AutoClose` only applies when the observable triggers the close.\n\n### Programmatic control\n\nWhen you need to close from outside the component (e.g. after an async save), use `showAndReturnRef`. The modal must declare an output type if you pass a payload to `close()`; returning data is optional — omit the second argument when you only need the result.\n\n```ts\n// SpinnerModal extends ModalContent\u003cvoid, { savedId: number }\u003e\nconst ref = this.modalieur.showAndReturnRef(SpinnerModal, { dismissible: false });\n\nref.closed$.subscribe(outcome =\u003e this.onSaveComplete(outcome));\n\nawait this.save();\nref.close(ModalResult.Ok, { savedId: 42 }); // payload optional when TDataOut is concrete\n```\n\n`ModalRef` is also injectable inside the modal component (via `ModalContent`'s internal wiring).\n\n### Custom layouts\n\n**Bootstrap fullscreen** — uses the built-in shell:\n\n```ts\nthis.modalieur.show(ConfirmModalComponent, { size: 'fullscreen', data });\n```\n\n**Fully custom overlay** — skip the Bootstrap shell:\n\n```ts\nthis.modalieur.show(MyOverlayComponent, { unstyled: true, data });\n```\n\nYour component owns the entire layout (positioning, z-index, animations). CDK still provides overlay, focus trap, and backdrop.\n\n### Lazy loading\n\n`show()` accepts a component **class** (`Type\u003cC\u003e`), not a route-style `loadComponent` loader. To lazy-load a modal, dynamically import it first, then pass the resolved class to `show()`.\n\n**Async/await:**\n\n```ts\nasync openLazyModal(): Promise\u003cvoid\u003e {\n  const { LazyLoadModalComponent } = await import('./modals/lazy-load-modal.component');\n\n  this.modalieur\n    .show(LazyLoadModalComponent)\n    .subscribe((outcome) =\u003e {\n      // outcome.result: ModalResult\n    });\n}\n```\n\n**RxJS:**\n\n```ts\nimport { from, switchMap } from 'rxjs';\n\nfrom(import('./modals/lazy-load-modal.component'))\n  .pipe(switchMap(({ LazyLoadModalComponent }) =\u003e this.modalieur.show(LazyLoadModalComponent)))\n  .subscribe(outcome =\u003e {\n    // outcome.result: ModalResult\n  });\n```\n\n**Avoid eager imports.** A top-level `import { LazyLoadModalComponent }` in code loaded at startup pulls the modal into the initial bundle. Use dynamic `import()` only where you open the modal.\n\nFor simple yes/no dialogs, `confirm()` and `messageBox()` avoid a custom component entirely — no separate chunk needed.\n\n## API reference\n\n### Public exports\n\nEverything in [`public-api.ts`](https://github.com/kazepis/ngx-modalieur/blob/HEAD/projects/ngx-modalieur/src/public-api.ts) is part of the stable API: `ModalieurService`, `ModalContent`, `ModalRef`, `ModalConfig`, `ModalOutcome`, `ModalResult`, `ModalSize`, `ModalDataIn`, `ModalDataOut`, `MODAL_DATA`, `MODALIEUR_CONFIG`, `MODALIEUR_DEFAULTS`, `provideModalieur`, `MessageBoxDialog`, `MessageBoxButtons`, `MessageBoxOptions`, `MESSAGE_BOX_TITLE_ID`, `MESSAGE_BOX_BODY_ID`, `BootstrapDialogContainer`.\n\n### `ModalieurService`\n\n| Method                                               | Returns                       | Description                                                                                    |\n| ---------------------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------- |\n| `show(component, config?)`                           | `Observable\u003cModalOutcome\u003cT\u003e\u003e` | Opens a modal; emits when it closes. `config` required (with `data`) when component has input. |\n| `showUntil(component, until$, config?)`              | `Observable\u003cModalOutcome\u003cT\u003e\u003e` | Auto-closes on first `until$` emission → `AutoClose`.                                          |\n| `showUntilCondition(component, condition$, config?)` | `Observable\u003cModalOutcome\u003cT\u003e\u003e` | Auto-closes on first truthy emission → `AutoClose`.                                            |\n| `showAndReturnRef(component, config?)`               | `ModalRef\u003cT\u003e`                 | Opens a modal; returns a ref for programmatic control.                                         |\n| `messageBox(options, config?)`                       | `Observable\u003cModalResult\u003e`     | Config-driven `MessageBoxDialog`.                                                              |\n| `confirm(title, message?, config?)`                  | `Observable\u003cModalResult\u003e`     | Yes / No message box.                                                                          |\n| `alert(title, message?, config?)`                    | `Observable\u003cModalResult\u003e`     | Single OK message box.                                                                         |\n\n### `ModalRef`\n\n| Member                  | Description                                                 |\n| ----------------------- | ----------------------------------------------------------- |\n| `id`                    | CDK dialog id                                               |\n| `closed$`               | `Observable\u003cModalOutcome\u003cT\u003e\u003e` — emits when the modal closes |\n| `close(result?, data?)` | Programmatic close; default result is `Undefined`           |\n\n### `ModalResult`\n\n| Value                       | When                                                |\n| --------------------------- | --------------------------------------------------- |\n| `Undefined`                 | Programmatic `close()` with no result               |\n| `Data`                      | `respondWithData()`                                 |\n| `Yes`, `No`, `Ok`, `Cancel` | Button helpers                                      |\n| `Abort`, `Retry`, `Ignore`  | Message-box helpers                                 |\n| `AutoClose`                 | `showUntil` / `showUntilCondition` auto-close       |\n| `Cancel`                    | User dismissal (backdrop / Escape) when dismissible |\n\n## Testing\n\nProvide a fake CDK `Dialog` and assert on `closed` emissions. See [`modalieur.service.spec.ts`](https://github.com/kazepis/ngx-modalieur/blob/HEAD/projects/ngx-modalieur/src/lib/modalieur.service.spec.ts) for the full pattern:\n\n```ts\nimport { Dialog } from '@angular/cdk/dialog';\nimport { TestBed } from '@angular/core/testing';\nimport { ModalieurService } from 'ngx-modalieur';\n\nTestBed.configureTestingModule({\n  providers: [ModalieurService, { provide: Dialog, useValue: fakeDialog }]\n});\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkazepis%2Fngx-modalieur","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkazepis%2Fngx-modalieur","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkazepis%2Fngx-modalieur/lists"}