npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@libs-ui/services-dialog

v0.2.357-32

Published

> Service quản lý Modal Dialog động trong Angular — tạo, hiển thị và đóng Dialog mà không cần khai báo thẻ trong template.

Readme

@libs-ui/services-dialog

Service quản lý Modal Dialog động trong Angular — tạo, hiển thị và đóng Dialog mà không cần khai báo thẻ trong template.

Giới thiệu

LibsUiDialogService cung cấp API đơn giản để hiển thị Modal Dialog tại bất kỳ đâu trong ứng dụng Angular thông qua Service, không cần khai báo component trong template HTML. Service quản lý toàn bộ vòng đời của Dialog — từ khởi tạo component động, gắn vào DOM, xử lý các event (agree/cancel/close/back), đến dọn dẹp khi đóng. Mỗi Dialog được định danh bằng UUID, cho phép quản lý nhiều Dialog đồng thời một cách độc lập.

Tính năng

  • ✅ Tạo Modal Dialog động tại runtime (không cần khai báo trong template)
  • ✅ Quản lý nhiều Dialog đồng thời qua Map với UUID unique
  • ✅ Xử lý 4 loại event: agree, cancel, close, back với callback async
  • ✅ Hỗ trợ tùy chỉnh footer với danh sách button tùy ý (buttonsFooter)
  • ✅ Auto-close sau event hoặc kiểm soát thủ công qua ignoreRemoveDialog
  • ✅ Toggle disable trạng thái toàn bộ Dialog để ngăn thao tác trùng lặp
  • ✅ Hỗ trợ cả Micro Frontend (isAddParentDocument)
  • ✅ Bảo mật XSS mặc định cho title (titleUseXssFilter: true)
  • ✅ Hai chế độ vị trí: center và offset-right

Khi nào sử dụng

  • Khi cần hiển thị Dialog/Modal từ service layer mà không muốn khai báo component trong template
  • Khi cần thông báo xác nhận (confirm) trước các thao tác destructive (xóa, reset, gửi)
  • Khi cần Dialog với async action (gọi API, loading state) trước khi đóng
  • Khi cần tạo Dialog linh hoạt với nhiều loại button tùy chỉnh ở footer
  • Khi cần quản lý nhiều Dialog đồng thời và có thể đóng từ bất kỳ component nào

Cài đặt

npm install @libs-ui/services-dialog

Import

import { LibsUiDialogService } from '@libs-ui/services-dialog';
import { IDialog, IDialogConfigEvent } from '@libs-ui/services-dialog';

Vì là Service (providedIn: 'root'), không cần khai báo trong imports[] của component. Chỉ cần inject:

import { Component, inject } from '@angular/core';
import { LibsUiDialogService } from '@libs-ui/services-dialog';

@Component({
  standalone: true,
  selector: 'app-my-feature',
  templateUrl: './my-feature.component.html',
})
export class MyFeatureComponent {
  private readonly dialogService = inject(LibsUiDialogService);
}

Ví dụ sử dụng

1. Dialog thông báo cơ bản

import { Component, inject } from '@angular/core';
import { LibsUiDialogService } from '@libs-ui/services-dialog';

@Component({
  standalone: true,
  selector: 'app-notification-demo',
  template: `<button (click)="handlerOpenNotice()">Thông báo</button>`,
})
export class NotificationDemoComponent {
  private readonly dialogService = inject(LibsUiDialogService);

  protected handlerOpenNotice(): void {
    const dialogId = this.dialogService.addDialog({
      title: 'Thông báo',
      bodyConfig: {
        lines: [{ text: 'Cập nhật dữ liệu thành công.' }],
      },
      buttonsFooter: [
        {
          label: 'Đóng',
          type: 'button-primary',
          action: async () => {
            this.dialogService.removeDialog(dialogId);
            await Promise.resolve();
          },
        },
      ],
    });
  }
}

2. Dialog xác nhận xóa với async action

import { Component, inject } from '@angular/core';
import { LibsUiDialogService } from '@libs-ui/services-dialog';

@Component({
  standalone: true,
  selector: 'app-delete-confirm',
  template: `<button (click)="handlerDeleteItem()">Xóa</button>`,
})
export class DeleteConfirmComponent {
  private readonly dialogService = inject(LibsUiDialogService);

  protected handlerDeleteItem(): void {
    const dialogId = this.dialogService.addDialog({
      title: 'Xác nhận xóa',
      width: '400px',
      bodyConfig: {
        iconType: 'warning',
        lines: [
          { text: 'Bạn có chắc chắn muốn xóa mục này không?' },
          { text: 'Hành động này không thể hoàn tác.', class: 'text-red-500' },
        ],
      },
      buttonsFooter: [
        {
          label: 'Hủy bỏ',
          type: 'button-primary-revert',
          action: async () => {
            this.dialogService.removeDialog(dialogId);
            await Promise.resolve();
          },
        },
        {
          label: 'Xóa ngay',
          type: 'button-danger-high',
          action: async () => {
            await this.deleteApi();
            this.dialogService.removeDialog(dialogId);
          },
        },
      ],
    });
  }

  private async deleteApi(): Promise<void> {
    // Gọi API xóa dữ liệu
  }
}

3. Dialog với loading state — ngăn auto-close

import { Component, inject } from '@angular/core';
import { LibsUiDialogService } from '@libs-ui/services-dialog';

@Component({
  standalone: true,
  selector: 'app-processing-dialog',
  template: `<button (click)="handlerSubmit()">Gửi dữ liệu</button>`,
})
export class ProcessingDialogComponent {
  private readonly dialogService = inject(LibsUiDialogService);

  protected handlerSubmit(): void {
    const dialogId = this.dialogService.addDialog({
      title: 'Xác nhận gửi',
      bodyConfig: {
        lines: [{ text: 'Bạn muốn gửi dữ liệu lên hệ thống?' }],
      },
      configAgreeEvent: {
        ignoreRemoveDialog: true, // Ngăn auto-close để chờ xử lý xong
        callback: async (control) => {
          control?.setStateDisable(true); // Disable nút tránh click nhiều lần
          try {
            await this.submitApi();
            this.dialogService.removeDialog(dialogId); // Đóng thủ công sau khi xong
          } catch {
            control?.setStateDisable(false); // Re-enable khi lỗi
          }
        },
      },
      configCancelEvent: {
        callback: async () => {
          await Promise.resolve();
        },
      },
    });
  }

  private async submitApi(): Promise<void> {
    // Gọi API submit dữ liệu
  }
}

4. Dialog với nhiều Dialog đồng thời và clearAll

import { Component, inject, signal } from '@angular/core';
import { LibsUiDialogService } from '@libs-ui/services-dialog';

@Component({
  standalone: true,
  selector: 'app-multi-dialog',
  template: `
    <button (click)="handlerOpenMultiple()">Mở nhiều Dialog</button>
    <button (click)="handlerClearAll()">Đóng tất cả</button>
  `,
})
export class MultiDialogComponent {
  private readonly dialogService = inject(LibsUiDialogService);
  private readonly openDialogIds = signal<string[]>([]);

  protected handlerOpenMultiple(): void {
    const id = this.dialogService.addDialog({
      title: `Dialog #${this.openDialogIds().length + 1}`,
      bodyConfig: {
        lines: [{ text: 'Dialog được mở tại runtime.' }],
      },
      zIndex: 1000 + this.openDialogIds().length * 10,
      buttonsFooter: [
        {
          label: 'Đóng',
          type: 'button-primary',
          action: async () => {
            this.dialogService.removeDialog(id);
            await Promise.resolve();
          },
        },
      ],
    });
    this.openDialogIds.update((ids) => [...ids, id]);
  }

  protected handlerClearAll(): void {
    this.dialogService.clearDialogsRef();
    this.openDialogIds.set([]);
  }
}

5. Dialog dạng offset-right (panel bên phải)

import { Component, inject } from '@angular/core';
import { LibsUiDialogService } from '@libs-ui/services-dialog';

@Component({
  standalone: true,
  selector: 'app-side-panel',
  template: `<button (click)="handlerOpenPanel()">Mở panel</button>`,
})
export class SidePanelComponent {
  private readonly dialogService = inject(LibsUiDialogService);

  protected handlerOpenPanel(): void {
    const dialogId = this.dialogService.addDialog({
      title: 'Chi tiết',
      mode: 'offset-right',
      width: '480px',
      height: '100%',
      bodyConfig: {
        lines: [{ text: 'Nội dung chi tiết hiển thị dạng panel bên phải.' }],
      },
      configCloseEvent: {
        callback: async () => {
          await Promise.resolve();
        },
      },
    });
  }
}

Methods

| Method | Signature | Mô tả | |---|---|---| | addDialog | (config: IDialog, isAddParentDocument?: boolean) => string | Tạo và hiển thị một Dialog mới. Trả về dialogId (UUID) để tham chiếu sau này | | removeDialog | (id: string) => void | Đóng và xóa Dialog theo ID. Bỏ qua nếu ID không tồn tại | | clearDialogsRef | () => void | Đóng và xóa tất cả Dialog đang mở | | switchDisableActionsOnDialog | (id: string) => void | Toggle trạng thái disable của Dialog theo ID (false → true → false) |

Types & Interfaces

import { IDialog, IDialogConfigEvent } from '@libs-ui/services-dialog';

IDialog

Cấu hình truyền vào addDialog():

| Property | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | title | string | - | Tiêu đề của Dialog | title: 'Xác nhận xóa' | | bodyConfig | IModalBodyConfig | - | Cấu hình nội dung Dialog (lines, icon, class) | bodyConfig: { lines: [{ text: 'Nội dung' }] } | | buttonsFooter | IButton[] | - | Danh sách button tùy chỉnh ở footer. Khi có, footer mặc định bị thay thế hoàn toàn | buttonsFooter: [{ label: 'OK', type: 'button-primary', action: async () => {} }] | | width | string | '500px' | Độ rộng của Dialog | width: '400px' | | height | string | 'auto' | Chiều cao của Dialog | height: '600px' | | maxWidth | string | - | Chiều rộng tối đa | maxWidth: '90vw' | | maxHeight | string | - | Chiều cao tối đa | maxHeight: '80vh' | | mode | 'center' \| 'offset-right' | 'center' | Vị trí hiển thị Dialog | mode: 'offset-right' | | zIndex | number | - | Thứ tự lớp hiển thị | zIndex: 1050 | | disable | boolean | false | Khóa toàn bộ tương tác trên Dialog | disable: true | | titleUseInnerText | boolean | false | Dùng innerText cho title (bảo mật, ngăn HTML injection) | titleUseInnerText: true | | titleUseXssFilter | boolean | true | Bật bộ lọc XSS cho title (mặc định bật) | titleUseXssFilter: false | | classIncludeModalWrapper | string | - | Class CSS thêm vào wrapper ngoài cùng của Modal | classIncludeModalWrapper: 'custom-dialog' | | headerConfig | IModalHeaderConfig | { hidden: !title } | Cấu hình header (ẩn/hiện, style). Mặc định ẩn nếu không có title | headerConfig: { hidden: false } | | footerConfig | IModalFooterConfig | - | Cấu hình footer container | footerConfig: { classInclude: 'justify-start' } | | configAgreeEvent | IDialogConfigEvent | - | Callback khi nhấn nút Đồng ý (Agree). Không hoạt động khi có buttonsFooter | xem bên dưới | | configCancelEvent | IDialogConfigEvent | - | Callback khi nhấn nút Hủy (Cancel). Không hoạt động khi có buttonsFooter | xem bên dưới | | configCloseEvent | IDialogConfigEvent | - | Callback khi nhấn nút Close (X) ở header | xem bên dưới | | configBackEvent | IDialogConfigEvent | - | Callback khi nhấn nút Back | xem bên dưới | | ignoreCommunicateMicroEvent | boolean | - | Bỏ qua xử lý event giao tiếp Micro Frontend | ignoreCommunicateMicroEvent: true |

IDialogConfigEvent

import { IDialogConfigEvent } from '@libs-ui/services-dialog';

// Cấu trúc interface
interface IDialogConfigEvent {
  callback: (functionsControl?: IModalFunctionsControl) => Promise<void>;
  ignoreRemoveDialog?: boolean; // true = Dialog không tự đóng sau event
}

Ví dụ sử dụng:

const config: IDialogConfigEvent = {
  ignoreRemoveDialog: true,
  callback: async (control) => {
    control?.setStateDisable(true); // Disable nút trong khi xử lý
    await someAsyncOperation();
    control?.setStateDisable(false);
  },
};

IModalBodyConfig (từ @libs-ui/components-modal)

import { IModalBodyConfig, IModalBodyLineConfig } from '@libs-ui/components-modal';

// Cấu trúc interface
interface IModalBodyConfig {
  classInclude?: string;       // Class CSS thêm vào body container
  hidden?: boolean;             // Ẩn/hiện body
  lines?: IModalBodyLineConfig[]; // Danh sách dòng nội dung
  iconType?: 'warning' | 'success' | 'fail' | 'information'; // Icon phía trên nội dung
  classIcon?: string;           // Class tùy chỉnh cho icon
}

interface IModalBodyLineConfig {
  text: string;          // Nội dung dòng text
  class?: string;        // Class CSS cho dòng này
  useXssFilter?: boolean; // Bật lọc XSS cho text này
}

Ví dụ với icon và nhiều dòng:

this.dialogService.addDialog({
  title: 'Cảnh báo',
  bodyConfig: {
    iconType: 'warning',
    lines: [
      { text: 'Thao tác này có thể ảnh hưởng đến dữ liệu hiện tại.' },
      { text: 'Vui lòng kiểm tra kỹ trước khi tiếp tục.', class: 'text-gray-500 text-sm' },
    ],
  },
});

IModalFunctionsControl (từ @libs-ui/components-modal)

Nhận được qua tham số control trong callback của config event:

interface IModalFunctionsControl {
  show: () => Promise<void>;                          // Hiển thị Modal
  hide: () => Promise<void>;                          // Ẩn Modal (không xóa)
  setStateDisable: (stateDisable: boolean) => Promise<void>; // Bật/tắt disable state
}

Lưu ý quan trọng

⚠️ buttonsFooter Override — configEvent KHÔNG hoạt động: Khi truyền buttonsFooter, footer mặc định bị thay thế hoàn toàn. Các button trong buttonsFooter chỉ gọi button.action(), không emit event agree/cancel. Do đó, configAgreeEvent và configCancelEvent sẽ không bao giờ được gọi. Chỉ configCloseEvent vẫn hoạt động vì được trigger bởi nút Close (X) ở header (không phải footer). Khuyến nghị: khi dùng buttonsFooter, xử lý toàn bộ logic trong button.action().

⚠️ Auto-close mặc định: Dialog tự đóng ngay sau khi callback của config event chạy xong (trừ khi ignoreRemoveDialog: true). Nếu cần chờ async operation xong mới đóng, set ignoreRemoveDialog: true và gọi removeDialog(id) thủ công.

⚠️ Singleton Service: Service là providedIn: 'root', tất cả component dùng chung một instance và cùng dialogsRef Map. dialogId trả về từ addDialog() có thể dùng để đóng Dialog từ bất kỳ component nào có biết ID đó.

⚠️ setStateDisable trong callback: Khi nhận event (agree/cancel/close/back), service tự động gọi functionControl.setStateDisable(true) trước khi gọi callback. Không cần gọi lại trong callback trừ khi muốn re-enable (setStateDisable(false)).

⚠️ isAddParentDocument cho Micro Frontend: Tham số thứ 2 của addDialog(). Khi true, Dialog sẽ được gắn vào parentDocument thay vì document hiện tại — dùng trong môi trường Micro Frontend để Dialog hiển thị đúng layer.