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/components-pages-template-detail

v0.2.357-4

Published

> Template layout chuẩn cho trang chi tiết — tích hợp header có nút quay lại, tiêu đề, và thanh action phía phải với nhiều loại control.

Readme

@libs-ui/components-pages-template-detail

Template layout chuẩn cho trang chi tiết — tích hợp header có nút quay lại, tiêu đề, và thanh action phía phải với nhiều loại control.

Giới thiệu

@libs-ui/components-pages-template-detail cung cấp hai component khung layout dành cho trang chi tiết (detail page): V1 (LibsUiComponentsPagesTemplateDetailComponent) và V2 (LibsUiComponentsPagesTemplateDetailV2Component). Cả hai đều bao gồm header chuẩn hóa với nút quay lại, tiêu đề có popover, và vùng action bên phải hỗ trợ nhiều loại control (button, switch, dropdown, radio group, tooltip-button). V2 bổ sung khả năng lazy-load component vào vùng body qua bodyConfig, skeleton loading, và ChangeDetectionStrategy.OnPush.

⚠️ Deprecated: LibsUiComponentsPagesTemplateDetailComponent (V1) đã bị deprecated. Ưu tiên sử dụng LibsUiComponentsPagesTemplateDetailV2Component (V2) cho mọi trang mới.

Tính năng

  • Header chuẩn hóa: Nút quay lại (chevron icon), nhãn "Quay lại danh sách", tiêu đề với popover tooltip khi hover.
  • Thanh action phải linh hoạt: Hỗ trợ button, swicth, menu-dropdown, button-dropdown, radio-group, tooltip-button, tooltip, circle-and-number trên cùng một thanh header.
  • Header tùy chỉnh tiêu đề phụ (header.content): V2 hỗ trợ dòng tiêu đề lớn phía trên dải header chính.
  • Chia tỉ lệ header 24/52/24: Bật isSplitHeaderRatio để header chia đều ba vùng trái — giữa — phải.
  • Vùng body lazy-load (V2): Lazy load component vào body qua bodyConfig.getComponentOutlet, hiển thị skeleton khi chưa resolve.
  • Skeleton loading (V2): Skeleton tùy chỉnh hoàn toàn qua bodyConfig.skeletonConfig.
  • Scroll overlay: Tích hợp sẵn LibsUiComponentsScrollOverlayDirective, emit event outScroll khi cuộn body.
  • FunctionControl API: Expose setStateDisable() qua outFunctionControl để component cha điều khiển trạng thái disable từ bên ngoài.
  • XSS-safe: Tiêu đề và mô tả tự động escape HTML qua LibsUiPipesEscapeHtmlPipe.
  • Angular Signals + OnPush (V2): Toàn bộ input dùng Signal API, V2 có ChangeDetectionStrategy.OnPush.

Khi nào sử dụng

  • Khi xây dựng trang chi tiết thực thể (Chi tiết khách hàng, Chi tiết đơn hàng, Chi tiết hợp đồng...).
  • Cần header thống nhất có đầy đủ action: nút Lưu, nút Hủy, dropdown thao tác, switch trạng thái.
  • Trang chi tiết có body là một component riêng cần lazy-load (dùng V2 + bodyConfig).
  • Khi muốn hiển thị tiêu đề trang ở giữa header (ví dụ: tên record đang xem), dùng isSplitHeaderRatio + configCenter.

Cài đặt

npm install @libs-ui/components-pages-template-detail

Import

// V2 (khuyến nghị cho code mới)
import { LibsUiComponentsPagesTemplateDetailV2Component } from '@libs-ui/components-pages-template-detail';

// V1 (deprecated — chỉ dùng khi maintain code cũ)
import { LibsUiComponentsPagesTemplateDetailComponent } from '@libs-ui/components-pages-template-detail';

// Interfaces & Types
import {
  IPagesTemplateDetailConfigTitle,
  IPagesTemplateDetailConfigRight,
  IPagesTemplateDetailConfigCenter,
  IPageDetailFunctionControl,
  IPageDetailV2BodyConfig,
  IPageDetailV2SectionData,
} from '@libs-ui/components-pages-template-detail';

Ví dụ sử dụng

Ví dụ 1 — Trang chi tiết cơ bản (V2)

// customer-detail.component.ts
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { LibsUiComponentsPagesTemplateDetailV2Component } from '@libs-ui/components-pages-template-detail';
import { IPageDetailFunctionControl, IPagesTemplateDetailConfigRight } from '@libs-ui/components-pages-template-detail';

@Component({
  selector: 'app-customer-detail',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsPagesTemplateDetailV2Component],
  template: `
    <libs_ui-components-pages_template-detail_v2
      [configTitle]="{
        config: { content: 'Nguyễn Văn An' },
        isShowBackToListLabel: true
      }"
      [configRight]="configRight"
      (outClose)="handlerClose($event)"
      (outFunctionControl)="handlerFunctionControl($event)">
    </libs_ui-components-pages_template-detail_v2>
  `,
})
export class CustomerDetailComponent {
  protected configRight: IPagesTemplateDetailConfigRight[] = [
    {
      key: 'button',
      configButton: {
        label: 'Lưu thay đổi',
        type: 'button-primary',
        action: () => this.handlerSave(),
      },
    },
    {
      key: 'button',
      configButton: {
        label: 'Hủy',
        type: 'button-secondary',
        action: () => this.handlerCancel(),
      },
    },
  ];

  private functionControl?: IPageDetailFunctionControl;

  protected handlerClose(event: Event): void {
    event.stopPropagation();
    // Navigate back to list
  }

  protected handlerFunctionControl(control: IPageDetailFunctionControl): void {
    this.functionControl = control;
  }

  private handlerSave(): void {
    // Save logic
  }

  private handlerCancel(): void {
    // Cancel logic
  }
}

Ví dụ 2 — Header chia 3 phần (24/52/24) với tiêu đề ở giữa

// order-detail.component.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { LibsUiComponentsPagesTemplateDetailV2Component } from '@libs-ui/components-pages-template-detail';
import {
  IPageDetailFunctionControl,
  IPagesTemplateDetailConfigCenter,
  IPagesTemplateDetailConfigRight,
} from '@libs-ui/components-pages-template-detail';

@Component({
  selector: 'app-order-detail',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsPagesTemplateDetailV2Component],
  template: `
    <libs_ui-components-pages_template-detail_v2
      [isSplitHeaderRatio]="true"
      [configTitle]="{
        config: { content: 'Mã đơn: #DH-2024-001' },
        isShowBackToListLabel: true
      }"
      [configCenter]="configCenter"
      [configRight]="configRight"
      (outClose)="handlerClose($event)"
      (outFunctionControl)="handlerFunctionControl($event)">
    </libs_ui-components-pages_template-detail_v2>
  `,
})
export class OrderDetailComponent {
  protected configCenter: IPagesTemplateDetailConfigCenter = {
    title: 'Chi tiết đơn hàng',
    classIncludeTitle: 'uppercase libs-ui-font-h4s',
  };

  protected configRight: IPagesTemplateDetailConfigRight[] = [
    {
      key: 'swicth',
      configSwicth: {
        active: true,
        action: async (event) => {
          // Handle switch change
        },
      },
    },
    {
      key: 'menu-dropdown',
      configDropdown: {
        listConfig: [
          { key: 'export', label: 'Xuất PDF' },
          { key: 'print', label: 'In đơn hàng' },
          { key: 'delete', label: 'Xóa đơn hàng' },
        ],
      },
    },
  ];

  protected handlerClose(event: Event): void {
    event.stopPropagation();
    // Navigate back
  }

  protected handlerFunctionControl(control: IPageDetailFunctionControl): void {
    // Store function control for external use
  }
}

Ví dụ 3 — Lazy-load body component (V2 + bodyConfig)

// contract-detail.component.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { from, of } from 'rxjs';
import { LibsUiComponentsPagesTemplateDetailV2Component } from '@libs-ui/components-pages-template-detail';
import {
  IPageDetailFunctionControl,
  IPageDetailV2BodyConfig,
  IPagesTemplateDetailConfigRight,
} from '@libs-ui/components-pages-template-detail';

@Component({
  selector: 'app-contract-detail',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsPagesTemplateDetailV2Component],
  template: `
    <libs_ui-components-pages_template-detail_v2
      [configTitle]="{
        config: { content: 'Hợp đồng số: HD-2024-001' },
        isShowBackToListLabel: true
      }"
      [configRight]="configRight"
      [bodyConfig]="bodyConfig"
      (outClose)="handlerClose($event)"
      (outFunctionControl)="handlerFunctionControl($event)">
    </libs_ui-components-pages_template-detail_v2>
  `,
})
export class ContractDetailComponent {
  protected bodyConfig: IPageDetailV2BodyConfig = {
    getComponentOutlet: () =>
      from(
        import('./contract-info/contract-info.component').then(
          (m) => m.ContractInfoComponent
        )
      ),
    getDataComponentOutlet: (sectionData) =>
      of({
        contractId: 'HD-2024-001',
        disable: sectionData?.disable ?? false,
      }),
    classInclude: 'p-[24px]',
  };

  protected configRight: IPagesTemplateDetailConfigRight[] = [
    {
      key: 'button',
      configButton: {
        label: 'Duyệt hợp đồng',
        type: 'button-primary',
        action: () => this.handlerApprove(),
      },
    },
  ];

  protected handlerClose(event: Event): void {
    event.stopPropagation();
    // Navigate back
  }

  protected handlerFunctionControl(control: IPageDetailFunctionControl): void {
    // Dùng control.setStateDisable(true) để disable toàn bộ action trong header
  }

  private handlerApprove(): void {
    // Approve logic
  }
}

Ví dụ 4 — Điều khiển trạng thái disable từ bên ngoài qua FunctionControl

// invoice-detail.component.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { LibsUiComponentsPagesTemplateDetailV2Component } from '@libs-ui/components-pages-template-detail';
import { IPageDetailFunctionControl, IPagesTemplateDetailConfigRight } from '@libs-ui/components-pages-template-detail';

@Component({
  selector: 'app-invoice-detail',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsPagesTemplateDetailV2Component],
  template: `
    <libs_ui-components-pages_template-detail_v2
      [configTitle]="{ config: { content: 'Hóa đơn #INV-001' } }"
      [configRight]="configRight"
      (outClose)="handlerClose($event)"
      (outFunctionControl)="handlerFunctionControl($event)"
      (outStateDisable)="handlerStateDisable($event)">
    </libs_ui-components-pages_template-detail_v2>
  `,
})
export class InvoiceDetailComponent {
  protected configRight: IPagesTemplateDetailConfigRight[] = [
    {
      key: 'button',
      configButton: {
        label: 'Lưu',
        type: 'button-primary',
        action: () => this.handlerSave(),
      },
    },
  ];

  private functionControl?: IPageDetailFunctionControl;

  protected handlerClose(event: Event): void {
    event.stopPropagation();
  }

  protected handlerFunctionControl(control: IPageDetailFunctionControl): void {
    this.functionControl = control;
  }

  protected handlerStateDisable(isDisabled: boolean): void {
    // React when disable state changes
  }

  private async handlerSave(): Promise<void> {
    // Disable all header actions while saving
    await this.functionControl?.setStateDisable(true);
    try {
      // ... save API call
    } finally {
      await this.functionControl?.setStateDisable(false);
    }
  }
}

@Input() — LibsUiComponentsPagesTemplateDetailV2Component

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | [bodyConfig] | IPageDetailV2BodyConfig | {} | Cấu hình lazy-load component vào vùng body. Khi không truyền getComponentOutlet, vùng body sẽ không render (dùng ng-content thay thế với V1). | [bodyConfig]="{ getComponentOutlet: () => from(import('./body.component').then(m => m.BodyComponent)) }" | | [classIncludeHeader] | string | undefined | CSS class bổ sung cho dải header. | [classIncludeHeader]="'border-b-2 border-blue-500'" | | [configCenter] | IPagesTemplateDetailConfigCenter | undefined | Cấu hình vùng giữa header — chỉ hiển thị khi isSplitHeaderRatiotrue. | [configCenter]="{ title: 'Tên bản ghi', classIncludeTitle: 'uppercase libs-ui-font-h4s' }" | | [configRight] | Array<IPagesTemplateDetailConfigRight> | undefined | Danh sách control hiển thị ở bên phải header, theo thứ tự từ trái sang phải. | [configRight]="[{ key: 'button', configButton: { label: 'Lưu', type: 'button-primary' } }]" | | [configTitle] | IPagesTemplateDetailConfigTitle | undefined | Cấu hình vùng bên trái header: nút quay lại, tiêu đề popover, mô tả phụ, tiêu đề phụ trên cùng. | [configTitle]="{ config: { content: 'Nguyễn Văn An' }, isShowBackToListLabel: true }" | | [disable] | boolean (model — two-way) | false | Trạng thái disable toàn bộ action trong header. Có thể bind two-way [(disable)]. | [(disable)]="isDisabled" | | [isSplitHeaderRatio] | boolean | undefined | Khi true, header chia thành 3 vùng với tỉ lệ 24% — 52% — 24%; vùng giữa hiển thị configCenter. | [isSplitHeaderRatio]="true" | | [zIndex] | number | 1000 | Giá trị z-index CSS của toàn bộ component. Tự động fallback về 1000 khi truyền undefined. | [zIndex]="1200" |

@Input() — LibsUiComponentsPagesTemplateDetailComponent (V1, deprecated)

Tương tự V2, ngoại trừ không có bodyConfig. V1 có thêm [classIncludeBody] để thêm class cho vùng body khi dùng ng-content.

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | [classIncludeBody] | string | undefined | CSS class bổ sung cho vùng body (chỉ V1, dùng với ng-content). | [classIncludeBody]="'p-[24px]'" | | [classIncludeHeader] | string | undefined | CSS class bổ sung cho dải header. | [classIncludeHeader]="'shadow-sm'" | | [configCenter] | IPagesTemplateDetailConfigCenter | undefined | Cấu hình vùng giữa header. | [configCenter]="{ title: 'Tên trang' }" | | [configRight] | Array<IPagesTemplateDetailConfigRight> | undefined | Danh sách control phía phải header. | [configRight]="actionList" | | [configTitle] | IPagesTemplateDetailConfigTitle | undefined | Cấu hình vùng trái header. | [configTitle]="{ config: { content: 'Tiêu đề' } }" | | [disable] | boolean (model) | false | Trạng thái disable toàn bộ action. | [(disable)]="isDisabled" | | [isSplitHeaderRatio] | boolean | undefined | Chia header theo tỉ lệ 24/52/24. | [isSplitHeaderRatio]="true" | | [zIndex] | number | 1000 | Giá trị z-index của component. | [zIndex]="1100" |

@Output()

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outClose) | boolean | Phát ra true khi người dùng click nút quay lại (chevron icon hoặc nhãn "Quay lại danh sách"). | handlerClose(event: Event): void { event.stopPropagation(); /* navigate back */ } | (outClose)="handlerClose($event)" | | (outFunctionControl) | IPageDetailFunctionControl | Phát ra object chứa các hàm điều khiển component từ bên ngoài (emit một lần trong ngOnInit). | handlerFunctionControl(ctrl: IPageDetailFunctionControl): void { this.control = ctrl; } | (outFunctionControl)="handlerFunctionControl($event)" | | (outScroll) | Event | Phát ra native scroll event khi người dùng cuộn vùng body. | handlerScroll(event: Event): void { event.stopPropagation(); /* handle scroll */ } | (outScroll)="handlerScroll($event)" | | (outSelectedButtonDropdown) | IEmitSelectKey | Phát ra khi người dùng chọn một item từ button-dropdown trong configRight. | handlerSelectedButtonDropdown(event: IEmitSelectKey): void { event.stopPropagation(); /* handle select */ } | (outSelectedButtonDropdown)="handlerSelectedButtonDropdown($event)" | | (outSelectedMenuDropdown) | IEmitSelectKey \| undefined | Phát ra khi người dùng chọn một item từ menu-dropdown trong configRight. | handlerSelectedMenuDropdown(event: IEmitSelectKey \| undefined): void { event.stopPropagation(); /* handle select */ } | (outSelectedMenuDropdown)="handlerSelectedMenuDropdown($event)" | | (outSelectedRadio) | IRadioEvent | Phát ra khi người dùng thay đổi lựa chọn trong radio-group trên header. | handlerSelectedRadio(event: IRadioEvent): void { event.stopPropagation(); /* handle change */ } | (outSelectedRadio)="handlerSelectedRadio($event)" | | (outStateDisable) | boolean | Phát ra giá trị disable mới mỗi khi setStateDisable() được gọi qua FunctionControl. | handlerStateDisable(isDisabled: boolean): void { /* react to disable change */ } | (outStateDisable)="handlerStateDisable($event)" | | (outTooltipButtonFunctionControl) | IPopoverFunctionControlEvent | Phát ra function control của popover/tooltip gắn với tooltip-button trong configRight. | handlerTooltipControl(event: IPopoverFunctionControlEvent): void { this.tooltipControl = event; } | (outTooltipButtonFunctionControl)="handlerTooltipControl($event)" |

Types & Interfaces

import {
  IPagesTemplateDetailConfigTitle,
  IPagesTemplateDetailConfigRight,
  IPagesTemplateDetailConfigCenter,
  IPageDetailFunctionControl,
  IPageDetailV2BodyConfig,
  IPageDetailV2SectionData,
} from '@libs-ui/components-pages-template-detail';

IPagesTemplateDetailConfigTitle

import { IPopover, IPopoverOverlay } from '@libs-ui/components-popover';

interface IPagesTemplateDetailConfigTitle extends IPopover {
  /** Tiêu đề phụ hiển thị dạng dải riêng phía trên header chính (chỉ V2) */
  header?: {
    content?: string;
    classInclude?: string;
  };
  /** Ẩn nút mũi tên quay lại (chevron icon). Mặc định: false */
  ignoreButtonBack?: boolean;
  /**
   * Tắt escape HTML tự động cho nội dung tiêu đề.
   * Chỉ dùng khi nội dung đã được sanitize từ nguồn tin cậy.
   * Mặc định: false (luôn escape)
   */
  ignoreEscapeHtml?: boolean;
  /** Hiển thị mũi tên (arrow begin) trước nút quay lại */
  isShowArrowBegin?: boolean;
  /** Hiển thị nhãn "Quay lại danh sách" dạng link button thay vì icon */
  isShowBackToListLabel?: boolean;
  /** Cấu hình mô tả phụ hiển thị bên cạnh tiêu đề chính */
  configDescription?: {
    innerView?: string;
    config: IPopoverOverlay;
  };
}

IPagesTemplateDetailConfigRight

import { IButton } from '@libs-ui/components-buttons-button';
import { IButtonDropdown } from '@libs-ui/components-buttons-dropdown';
import { IDropdown } from '@libs-ui/components-dropdown';
import { IPopover } from '@libs-ui/components-popover';
import { IRadioGroupItem } from '@libs-ui/components-radio-group';
import { ISwitch } from '@libs-ui/components-switch';

type ButtonKey =
  | 'button'
  | 'swicth'
  | 'radio-group'
  | 'circle-and-number'
  | 'button-dropdown'
  | 'menu-dropdown'
  | 'tooltip-button'
  | 'tooltip';

interface IPagesTemplateDetailConfigRight {
  /** Loại control cần hiển thị */
  key: ButtonKey;
  /** CSS class bổ sung cho wrapper của control này */
  classInclude?: string;
  /** Trạng thái disable riêng cho control này (ưu tiên hơn disable toàn component) */
  disable?: boolean;
  /** Hiển thị loading spinner trên control */
  isPending?: boolean;
  /** Ẩn control này (dùng để điều kiện hiện/ẩn mà không cần unmount) */
  ignoreShowButton?: boolean;
  /** Cấu hình cho key: 'button' */
  configButton?: IButton;
  /** Cấu hình cho key: 'button-dropdown' */
  configButtonDropdown?: IButtonDropdown;
  /** Cấu hình cho key: 'radio-group' */
  configRadioGroup?: Array<IRadioGroupItem>;
  /** Cấu hình cho key: 'swicth' */
  configSwicth?: ISwitch;
  /** Cấu hình cho key: 'menu-dropdown' */
  configDropdown?: IDropdown;
  /** Cấu hình cho key: 'tooltip-button' */
  configTooltipButton?: {
    configTooltip?: IPopover;
    configButton?: IButton;
  };
}

IPagesTemplateDetailConfigCenter

import { TemplateRef } from '@angular/core';
import { TYPE_TEMPLATE_REF } from '@libs-ui/interfaces-types';

interface IPagesTemplateDetailConfigCenter {
  /** Text tiêu đề (tự động translate và escape HTML) */
  title?: string;
  /** CSS class bổ sung cho phần tử tiêu đề */
  classIncludeTitle?: string;
  /** TemplateRef tùy chỉnh thay thế hoàn toàn vùng giữa khi cần UI phức tạp hơn */
  template?: TemplateRef<TYPE_TEMPLATE_REF>;
}

IPageDetailFunctionControl

interface IPageDetailFunctionControl {
  /**
   * Thay đổi trạng thái disable của toàn bộ action trong header.
   * Gọi setStateDisable(true) trước khi submit, setStateDisable(false) sau khi hoàn thành.
   */
  setStateDisable: (stateDisable: boolean) => Promise<void>;
}

IPageDetailV2BodyConfig (chỉ V2)

import { WritableSignal } from '@angular/core';
import { ISkeletonConfig } from '@libs-ui/components-skeleton';
import { TYPE_FUNCTION } from '@libs-ui/interfaces-types';

interface IPageDetailV2BodyConfig {
  /**
   * Hàm trả về Observable<Type> để lazy-load component vào vùng body.
   * Nhận tham số `sectionData: IPageDetailV2SectionData` (có `disable`).
   * Khi chưa resolve, hiển thị skeleton.
   */
  getComponentOutlet?: TYPE_FUNCTION<any>;
  /**
   * Hàm trả về Observable<Record> chứa data truyền vào component được load.
   * Nhận tham số `sectionData: IPageDetailV2SectionData`.
   */
  getDataComponentOutlet?: TYPE_FUNCTION<Record<string, unknown>>;
  /** Cấu hình skeleton hiển thị trong lúc đang lazy-load component */
  skeletonConfig?: WritableSignal<ISkeletonConfig>;
  /** CSS class bổ sung cho wrapper của vùng body */
  classInclude?: string;
}

interface IPageDetailV2SectionData {
  /** Trạng thái disable hiện tại của component — dùng để truyền xuống body component */
  disable: boolean;
}

Lưu ý quan trọng

⚠️ V1 đã deprecated: LibsUiComponentsPagesTemplateDetailComponent (selector libs_ui-components-pages_template-detail) không còn được duy trì. Mọi trang mới BẮT BUỘC dùng LibsUiComponentsPagesTemplateDetailV2Component.

⚠️ Lỗi chính tả swicth: Interface dùng configSwicth (không phải configSwitch) và key 'swicth' — đây là tên gốc trong source, cần giữ đúng chính xác khi sử dụng.

⚠️ bodyConfig chỉ hoạt động với getComponentOutlet: Khi bodyConfig.getComponentOutlet không được truyền, V2 sẽ không render vùng body tự động. Cần dùng ng-content (chỉ V1 hỗ trợ) hoặc truyền đủ cấu hình getComponentOutlet.

⚠️ outFunctionControl emit một lần duy nhất: outFunctionControl chỉ emit trong ngOnInit. Phải lưu giá trị vào biến class để dùng sau: private functionControl?: IPageDetailFunctionControl.

⚠️ XSS tự động: Tiêu đề trong configTitle.config.content mặc định được escape HTML. Nếu cần render HTML thực (ví dụ: i18n có HTML), truyền ignoreEscapeHtml: true — chỉ dùng khi nguồn dữ liệu là i18n key do team kiểm soát.

⚠️ isSplitHeaderRatio + configCenter: configCenter chỉ hiển thị khi isSplitHeaderRatiotrue. Truyền configCenter mà không bật isSplitHeaderRatio sẽ không có effect.

⚠️ Dynamic component với V2: Khi dùng V2 làm dynamic component trong Modal hoặc page template khác, BẮT BUỘC khai báo Generic Type rõ ràng cho ComponentRef và gọi .destroy() trong ngOnDestroy của component cha để tránh memory leak.

Demo

npx nx serve core-ui

Truy cập: http://localhost:4500/ và tìm mục "pages-template-detail" trong menu demo.