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-dynamic-component

v0.2.357-9

Published

> Service tạo và quản lý Angular component động tại runtime — gắn vào body, element cụ thể, hoặc parent document trong kịch bản iframe.

Downloads

3,004

Readme

@libs-ui/services-dynamic-component

Service tạo và quản lý Angular component động tại runtime — gắn vào body, element cụ thể, hoặc parent document trong kịch bản iframe.

Giới thiệu

LibsUiDynamicComponentService cung cấp API đầy đủ để tạo Angular component bằng code TypeScript thuần (không khai báo trong template HTML), đính kèm vào bất kỳ vị trí nào trên DOM, và dọn dẹp đúng cách khi không còn cần thiết. Ngoài ra, package còn xuất kèm hai hàm tiện ích setInputs / setInput giúp truyền input type-safe vào ComponentRef đã tạo.

Tính năng

  • Tạo Angular component động qua createComponent API (Angular 14+)
  • Gắn component vào document.body, element bất kỳ, hoặc element theo id
  • Hỗ trợ kịch bản iframe: gắn component vào window.parent.document.body và tự động sao chép style sang parent document
  • Cache DOM element theo id bằng Map để tránh query DOM nhiều lần
  • Xóa component khỏi DOM với tuỳ chọn: xóa ngay, delay destroy, hoặc chỉ detach (giữ lại để reuse)
  • Hàm setInputs / setInput truyền input type-safe vào ComponentRef
  • Singleton (providedIn: 'root') — dùng xuyên suốt ứng dụng

Khi nào sử dụng

  • Tạo modal, dialog, toast, tooltip động từ TypeScript mà không cần khai báo trong template
  • Gắn component vào vị trí DOM tuỳ ý (outside Angular host tree)
  • Trong kịch bản micro-frontend / iframe: cần render component ở parent window
  • Cần kiểm soát lifecycle component động (create → attach → detach → destroy)
  • Dùng kèm với LibsUiComponentsModalV2Component hoặc các template page component

Cài đặt

npm install @libs-ui/services-dynamic-component

Import

import { LibsUiDynamicComponentService, setInputs, setInput } from '@libs-ui/services-dynamic-component';

Ví dụ sử dụng

Ví dụ 1 — Tạo component và gắn vào document.body

import { Component, ComponentRef, inject } from '@angular/core';
import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';
import { MyToastComponent } from './my-toast.component';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: `<button (click)="handlerShowToast($event)">Hiện toast</button>`,
})
export class DashboardComponent {
  private readonly dynamicService = inject(LibsUiDynamicComponentService);
  private toastRef?: ComponentRef<MyToastComponent>;

  handlerShowToast(event: Event): void {
    event.stopPropagation();
    this.toastRef = this.dynamicService.resolveComponentFactory(MyToastComponent);
    this.dynamicService.addToBody(this.toastRef);
  }

  handlerHideToast(event: Event): void {
    event.stopPropagation();
    this.dynamicService.delete(this.toastRef);
    this.toastRef = undefined;
  }
}

Ví dụ 2 — Gắn component vào element có id cụ thể

import { Component, ComponentRef, inject } from '@angular/core';
import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';
import { MyPreviewComponent } from './my-preview.component';

@Component({
  selector: 'app-file-list',
  standalone: true,
  template: `
    <div id="preview-panel" class="min-h-[200px] border rounded-lg"></div>
    <button (click)="handlerOpenPreview($event)">Xem trước</button>
  `,
})
export class FileListComponent {
  private readonly dynamicService = inject(LibsUiDynamicComponentService);
  private previewRef?: ComponentRef<MyPreviewComponent>;

  handlerOpenPreview(event: Event): void {
    event.stopPropagation();
    this.previewRef = this.dynamicService.resolveComponentFactory(MyPreviewComponent);
    this.dynamicService.addToIdAttributeElement(this.previewRef, 'preview-panel');
  }

  handlerClosePreview(event: Event): void {
    event.stopPropagation();
    this.dynamicService.delete(this.previewRef);
    this.previewRef = undefined;
  }
}

Ví dụ 3 — Truyền input type-safe với setInputs

import { Component, ComponentRef, inject } from '@angular/core';
import { LibsUiDynamicComponentService, setInputs } from '@libs-ui/services-dynamic-component';
import { LibsUiComponentsModalV2Component } from '@libs-ui/components-modal';
import { ExtractInputs } from '@libs-ui/interfaces-types';
import { from, of } from 'rxjs';
import { MyModalBodyComponent, IMyModalBodyInputs } from './my-modal-body.component';

@Component({
  selector: 'app-order-list',
  standalone: true,
  template: `<button (click)="handlerOpenModal($event)">Mở modal</button>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OrderListComponent {
  private readonly dynamicService = inject(LibsUiDynamicComponentService);
  private modalRef?: ComponentRef<LibsUiComponentsModalV2Component>;

  handlerOpenModal(event: Event): void {
    event.stopPropagation();
    this.modalRef = this.dynamicService.resolveComponentFactory(LibsUiComponentsModalV2Component);
    setInputs(this.modalRef, {
      bodyConfig: {
        component: () => from(import('./my-modal-body.component').then((m) => m.MyModalBodyComponent)),
        getDataComponentOutlet: () => of<ExtractInputs<IMyModalBodyInputs>>({ orderId: '123', mode: 'view' }),
      },
    });
    this.dynamicService.addToBody(this.modalRef);
  }

  handlerCloseModal(event: Event): void {
    event.stopPropagation();
    this.dynamicService.delete(this.modalRef);
    this.modalRef = undefined;
  }
}

Ví dụ 4 — Gắn component vào parent document (kịch bản iframe)

import { Component, ComponentRef, inject } from '@angular/core';
import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';
import { MyOverlayComponent } from './my-overlay.component';

@Component({
  selector: 'app-iframe-content',
  standalone: true,
  template: `<button (click)="handlerOpenOverlay($event)">Mở overlay ở parent</button>`,
})
export class IframeContentComponent {
  private readonly dynamicService = inject(LibsUiDynamicComponentService);
  private overlayRef?: ComponentRef<MyOverlayComponent>;

  handlerOpenOverlay(event: Event): void {
    event.stopPropagation();
    // isAddParentDocument = true: gắn vào window.parent.document.body
    // Styles từ iframe tự động được sao chép sang parent document (delay 250ms)
    this.overlayRef = this.dynamicService.resolveComponentFactory(MyOverlayComponent);
    this.dynamicService.addToBody(this.overlayRef, true);
  }

  handlerCloseOverlay(event: Event): void {
    event.stopPropagation();
    // Khi delete: styles đã sao chép cũng được dọn sạch khỏi parent document
    this.dynamicService.delete(this.overlayRef);
    this.overlayRef = undefined;
  }
}

Ví dụ 5 — Xóa component với delay (dùng cho animation)

import { Component, ComponentRef, inject } from '@angular/core';
import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';
import { MyAnimatedComponent } from './my-animated.component';

@Component({
  selector: 'app-animated-wrapper',
  standalone: true,
  template: `<button (click)="handlerDismiss($event)">Đóng (có animation)</button>`,
})
export class AnimatedWrapperComponent {
  private readonly dynamicService = inject(LibsUiDynamicComponentService);
  private panelRef?: ComponentRef<MyAnimatedComponent>;

  handlerOpen(event: Event): void {
    event.stopPropagation();
    this.panelRef = this.dynamicService.resolveComponentFactory(MyAnimatedComponent);
    this.dynamicService.addToBody(this.panelRef);
  }

  handlerDismiss(event: Event): void {
    event.stopPropagation();
    // detachView() ngay lập tức, gọi destroy() sau 300ms để animation kết thúc
    this.dynamicService.delete(this.panelRef, { timeoutDestroy: 300 });
    this.panelRef = undefined;
  }
}

Methods

LibsUiDynamicComponentService

| Method | Signature | Mô tả | |---|---|---| | resolveComponentFactory | (component: Type<T>): ComponentRef<T> | Tạo ComponentRef từ class component. Sử dụng createComponent API với injector của ApplicationRef. | | addToBody | (componentRef?: ComponentRef<any>, isAddParentDocument?: boolean, timerDelayUpdateStyleToParent?: number): void | Gắn component vào document.body. Nếu isAddParentDocument = true, gắn vào window.parent.document.body và sao chép styles (mặc định delay 250ms). | | addToElement | (componentRef: any, elementAdd: HTMLElement): HTMLElement \| undefined | Gắn component vào element DOM bất kỳ. | | addToElementLayoutContentDefault | (componentRef: any, id?: string): void | Gắn component vào element có id bằng idElementLayoutContentDefault (mặc định "libs-ui-layout-content"). | | addToIdAttributeElement | (componentRef: any, id: string): HTMLElement \| undefined | Gắn component vào element có id chỉ định. Element được cache trong Map — lần sau không query DOM lại. | | delete | (componentRef: any, options?: { ignoreDestroyComponent?: boolean; timeoutDestroy?: number }): void | Detach view khỏi ApplicationRef, dọn sạch styles parent document (nếu có), rồi destroy component. | | remove | (componentRef: any, _?: string, ignoreDestroyComponent?: boolean): void | Deprecated — dùng delete() thay thế. |

delete() — Options chi tiết

| Option | Type | Mô tả | |---|---|---| | ignoreDestroyComponent | boolean | Chỉ detach view, không gọi destroy(). Dùng khi muốn tái sử dụng component sau. | | timeoutDestroy | number | Gọi destroy() sau N milliseconds. Detach view xảy ra ngay lập tức. Dùng cho animation close. |

IdElementLayoutContentDefault (setter)

// Thay đổi id mặc định cho addToElementLayoutContentDefault()
this.dynamicService.IdElementLayoutContentDefault = 'my-custom-layout-id';

Hàm tiện ích — setInputs / setInput

setInputs

Truyền nhiều input cùng lúc vào ComponentRef với type-safety đầy đủ nhờ ExtractInputs.

import { setInputs } from '@libs-ui/services-dynamic-component';
import { ExtractInputs } from '@libs-ui/interfaces-types';

const modalRef = this.dynamicService.resolveComponentFactory(MyModalComponent);
setInputs(modalRef, {
  title: 'Xác nhận xoá',
  confirmLabel: 'Xoá',
  itemId: 'abc-123',
} as ExtractInputs<MyModalComponent>);

Signature đầy đủ:

setInputs<T, R extends boolean = true>(
  componentRef: ComponentRef<T>,
  inputs: ExtractInputs<T, R>,
  required?: R
): void

setInput

Truyền từng input riêng lẻ, an toàn với keyof.

import { setInput } from '@libs-ui/services-dynamic-component';

const panelRef = this.dynamicService.resolveComponentFactory(MyPanelComponent);
setInput(panelRef, 'title', 'Chi tiết đơn hàng');
setInput(panelRef, 'orderId', 'ORD-456');

Signature đầy đủ:

setInput<T>(
  componentRef: ComponentRef<T>,
  input: keyof ExtractInputs<T>,
  value: ExtractInputs<T>[keyof ExtractInputs<T>]
): void

Types & Interfaces

Package sử dụng ExtractInputs từ @libs-ui/interfaces-types:

import { ExtractInputs } from '@libs-ui/interfaces-types';

// Dùng để type-check khi gọi setInputs()
type T_MyComponentInputs = ExtractInputs<MyComponent>;

Lưu ý quan trọng

⚠️ Singleton & Shared State: Service là providedIn: 'root' — toàn bộ ứng dụng dùng chung một instance. elementLayoutContent Map và elementBody được cache và chia sẻ giữa tất cả usages. Tránh nhiều component cùng ghi vào cùng một container id đồng thời nếu không kiểm soát được thứ tự.

⚠️ Stale Cache: Nếu element có id bị xóa khỏi DOM sau khi đã được cache, addToIdAttributeElement vẫn giữ reference cũ và có thể không append được. Cần tạo lại element hoặc xóa cache bằng cách khởi động lại flow.

⚠️ Memory Leak: Luôn gọi delete(componentRef) khi component không còn dùng đến. Không gọi destroy() sẽ khiến component tồn tại trong memory. Lưu ComponentRef là class property (không phải local variable) để có thể cleanup trong ngOnDestroy.

⚠️ Styles Parent Document: Khi dùng isAddParentDocument = true, styles được clone và copy sang parent document. Khi gọi delete() — styles sẽ được tự động dọn sạch. Nếu service bị destroy mà không gọi delete() trước — styles trong parent document vẫn còn và phải dọn thủ công.

⚠️ remove() Deprecated: Phương thức remove() đã bị đánh dấu @deprecated. Luôn dùng delete() thay thế.

⚠️ ComponentRef phải là class property: Lưu ComponentRef ở cấp class (không phải const trong hàm) để có thể gọi delete() sau này và tránh memory leak.

// ❌ SAI — local variable, không thể cleanup
handlerOpen() {
  const ref = this.dynamicService.resolveComponentFactory(MyComponent);
  this.dynamicService.addToBody(ref);
}

// ✅ ĐÚNG — class property, dọn được trong ngOnDestroy
private panelRef?: ComponentRef<MyComponent>;

handlerOpen(event: Event) {
  event.stopPropagation();
  this.panelRef = this.dynamicService.resolveComponentFactory(MyComponent);
  this.dynamicService.addToBody(this.panelRef);
}

ngOnDestroy() {
  this.dynamicService.delete(this.panelRef);
}