@libs-ui/components-pages-template-full-screen
v0.2.357-4
Published
> Khung layout toàn màn hình (Full Screen) cho Angular — tích hợp header đa vùng với action linh hoạt, giao tiếp micro-frontend, và hỗ trợ lazy load body component (V2).
Readme
@libs-ui/components-pages-template-full-screen
Khung layout toàn màn hình (Full Screen) cho Angular — tích hợp header đa vùng với action linh hoạt, giao tiếp micro-frontend, và hỗ trợ lazy load body component (V2).
Giới thiệu
Package này cung cấp hai component layout toàn màn hình cho Angular:
LibsUiComponentsPagesTemplateFullScreenComponent(V1 —@deprecated): Component gốc, sử dụngng-contentcho phần body. Phù hợp với codebase cũ nhưng không còn được khuyến khích dùng cho code mới.LibsUiComponentsPagesTemplateFullScreenV2Component(V2 — khuyến dùng): Phiên bản nâng cấp, hỗ trợ lazy load body component động quabodyConfig, skeleton tự động trong lúc chờ load, vàFunctionsControlAPI để điều khiển từ component con.
Cả hai đều tích hợp sẵn cơ chế PostMessage để đồng bộ trạng thái "đang mở" với ứng dụng cha (micro-frontend host).
Tính năng
- Header 3 vùng (Trái / Giữa / Phải) với phân chia class tùy chỉnh
- Hỗ trợ các loại action trên header:
button,button-status,switch,label,line-space,button-dropdown - TemplateRef tùy chỉnh cho từng vùng header (
leftTemplate,centerTemplate,templateRight) - Menu dropdown "ba chấm" tích hợp sẵn (
menuDropDownConfigs) - Tự động escape HTML cho tiêu đề (XSS protection)
- Giao tiếp micro-frontend: gửi
PostMessage open/closekhi mount/unmount, ping định kỳ mỗi 2 giây - (V2) Lazy load body component qua
Observable<Type<any>> - (V2) Skeleton tự động hiển thị trong lúc chờ Observable resolve
- (V2)
FunctionsControl— API để component con chủ động đóng page - (V2)
ChangeDetectionStrategy.OnPush+ Angular Signals
Khi nào sử dụng
- Xây dựng màn hình cấu hình workflow, editor chuyên sâu chiếm trọn viewport
- Các trang báo cáo dữ liệu lớn cần tối đa không gian hiển thị
- Tạo mới thực thể phức tạp với quy trình nhiều bước (wizard-style)
- Khi cần đồng bộ trạng thái "đang mở" với ứng dụng cha qua
PostMessage - Khi muốn mở trang full screen từ TypeScript (không cần selector HTML) thông qua
LibsUiDynamicComponentService - Khi body cần được lazy load theo bundle split với skeleton trong lúc chờ (dùng V2)
Cài đặt
npm install @libs-ui/components-pages-template-full-screenImport
// V2 (khuyến dùng)
import { LibsUiComponentsPagesTemplateFullScreenV2Component } from '@libs-ui/components-pages-template-full-screen';
// V1 (deprecated — chỉ dùng khi maintain code cũ)
import { LibsUiComponentsPagesTemplateFullScreenComponent } from '@libs-ui/components-pages-template-full-screen';
// Types & Interfaces
import {
IPagesTemplateFullScreenButton,
IPagesTemplateFullScreenButtonKey,
IFullScreenV2BodyConfig,
IFullScreenV2SectionData,
IFunctionControlPageFullScreen,
} from '@libs-ui/components-pages-template-full-screen';Ví dụ sử dụng — V1 (cơ bản, dùng ng-content)
Ví dụ 1: Trang full screen đơn giản
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
IPagesTemplateFullScreenButton,
LibsUiComponentsPagesTemplateFullScreenComponent,
} from '@libs-ui/components-pages-template-full-screen';
@Component({
selector: 'app-campaign-create',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [LibsUiComponentsPagesTemplateFullScreenComponent],
template: `
<libs_ui-components-pages_template_full_screen
[title]="'Tạo chiến dịch Marketing'"
[labelLeft]="'Hủy bỏ'"
[buttonRight]="buttonRight"
(outClose)="handlerClose()"
(outClickButton)="handlerClickButton($event)">
<div class="p-[24px]">
<h2 class="libs-ui-font-h3s">Bước 1: Thông tin cơ bản</h2>
<!-- Nội dung trang -->
</div>
</libs_ui-components-pages_template_full_screen>
`,
})
export class AppCampaignCreateComponent {
readonly buttonRight: IPagesTemplateFullScreenButton[] = [
{ key: 'button', label: 'Lưu nháp', type: 'button-secondary' },
{ key: 'button', label: 'Tiếp tục', type: 'button-primary' },
];
handlerClose(): void {
// event.stopPropagation() không cần thiết ở đây vì outClose không bubble DOM
console.log('Đóng trang full screen');
}
handlerClickButton(button: IPagesTemplateFullScreenButton): void {
if (button.label === 'Tiếp tục') {
// Xử lý chuyển bước
}
}
}Ví dụ 2: Header với switch, dropdown menu và edit icon
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import {
IPagesTemplateFullScreenButton,
LibsUiComponentsPagesTemplateFullScreenComponent,
} from '@libs-ui/components-pages-template-full-screen';
import { IDropdown, IEmitSelectKey } from '@libs-ui/components-dropdown';
@Component({
selector: 'app-report-editor',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [LibsUiComponentsPagesTemplateFullScreenComponent],
template: `
<libs_ui-components-pages_template_full_screen
[title]="'Báo cáo doanh thu Q2/2026'"
[hasEdit]="true"
[buttonRight]="buttonRight"
[menuDropDownConfigs]="menuConfig"
[ignoreClosePageFullEvent]="true"
(outClose)="handlerClose()"
(outEdit)="handlerEdit()"
(outClickButton)="handlerClickButton($event)"
(outSelectedMenuDropdown)="handlerMenuSelect($event)">
<div class="p-[24px]">
<!-- Nội dung báo cáo -->
</div>
</libs_ui-components-pages_template_full_screen>
`,
})
export class AppReportEditorComponent {
readonly isPublished = signal(false);
readonly buttonRight: IPagesTemplateFullScreenButton[] = [
{
key: 'switch',
active: false,
classInclude: 'mr-[8px]',
action: async (event) => {
this.isPublished.set(event?.state ?? false);
},
},
{ key: 'line-space' },
{ key: 'button', label: 'Xuất PDF', type: 'button-secondary' },
{ key: 'button', label: 'Lưu', type: 'button-primary' },
];
readonly menuConfig: IDropdown = {
listConfig: {
type: 'text',
httpRequestData: signal({
objectInstance: {
list: [
{ key: 'duplicate', label: 'Nhân bản' },
{ key: 'archive', label: 'Lưu trữ' },
{ key: 'delete', label: 'Xóa vĩnh viễn' },
],
},
functionName: 'list',
argumentsValue: [],
}),
configTemplateText: signal({
fieldKey: 'key',
notUseVirtualScroll: true,
getConfigButtonLeft: (item: { key: string; label: string }) => ({
label: item.label,
type: 'button-link-third',
classLabel: 'libs-ui-font-h5r',
}),
}),
},
};
handlerClose(): void {
console.log('Đóng trang');
}
handlerEdit(): void {
console.log('Mở chỉnh sửa tiêu đề');
}
handlerClickButton(button: IPagesTemplateFullScreenButton): void {
console.log('Click button:', button.label);
}
handlerMenuSelect(event: IEmitSelectKey | undefined): void {
console.log('Menu chọn:', event?.key);
}
}Ví dụ sử dụng — V2 (khuyến dùng, mở động qua DynamicComponentService)
Ví dụ 3: Mở full screen V2 từ TypeScript với lazy load body
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { from, of } from 'rxjs';
import {
IFullScreenV2BodyConfig,
IPagesTemplateFullScreenButton,
LibsUiComponentsPagesTemplateFullScreenV2Component,
} from '@libs-ui/components-pages-template-full-screen';
import { ISkeletonConfig } from '@libs-ui/components-skeleton';
import { LibsUiDynamicComponentService, setInputs } from '@libs-ui/services-dynamic-component';
@Component({
selector: 'app-order-list',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<button (click)="handlerOpenDetail()">Xem chi tiết đơn hàng</button>
`,
})
export class AppOrderListComponent {
private readonly dynamicService = inject(LibsUiDynamicComponentService);
readonly skeletonConfig = signal<ISkeletonConfig>({
repeat: 1,
rows: [
{ item: { classInclude: 'w-full h-[56px] rounded-lg mb-[8px]' } },
{
cols: [
{ item: { classInclude: 'w-1/3 h-[80px] rounded-lg' } },
{ item: { classInclude: 'w-1/3 h-[80px] rounded-lg' } },
{ item: { classInclude: 'w-1/3 h-[80px] rounded-lg' } },
],
},
{ item: { classInclude: 'w-full h-[64px] rounded-xl mt-[16px]' } },
],
});
readonly bodyConfig: IFullScreenV2BodyConfig = {
getComponentOutlet: () =>
from(import('./order-detail/order-detail.component').then((c) => c.OrderDetailComponent)),
getDataComponentOutlet: (sectionData) =>
of({ disable: sectionData.item.disable, orderId: 'OD-4521' }),
skeletonConfig: this.skeletonConfig,
classInclude: 'overflow-y-auto',
};
readonly buttonRight: IPagesTemplateFullScreenButton[] = [
{ key: 'button', label: 'In hóa đơn', type: 'button-secondary' },
{ key: 'button', label: 'Xác nhận giao', type: 'button-primary' },
];
handlerOpenDetail(): void {
const fullScreenRef = this.dynamicService.resolveComponentFactory(
LibsUiComponentsPagesTemplateFullScreenV2Component,
);
setInputs(fullScreenRef, {
title: 'Chi tiết đơn hàng #OD-4521',
labelLeft: 'Quay lại',
buttonRight: this.buttonRight,
bodyConfig: this.bodyConfig,
zIndex: 9999,
});
fullScreenRef.instance.outClose.subscribe(() => {
this.dynamicService.delete(fullScreenRef);
});
document.getElementsByTagName('main')?.[0]?.classList.add('relative');
this.dynamicService.addToElement(fullScreenRef, document.getElementsByTagName('main')?.[0]);
}
}Ví dụ 4: Sử dụng FunctionsControl để đóng page từ component con
// Trong parent component — lưu ref để gọi FunctionsControl
import { ComponentRef } from '@angular/core';
import { LibsUiComponentsPagesTemplateFullScreenV2Component } from '@libs-ui/components-pages-template-full-screen';
// Khai báo ở cấp class (không phải const trong hàm)
private fullScreenRef: ComponentRef<LibsUiComponentsPagesTemplateFullScreenV2Component> | undefined;
handlerOpen(): void {
this.fullScreenRef = this.dynamicService.resolveComponentFactory(
LibsUiComponentsPagesTemplateFullScreenV2Component,
);
// Lấy FunctionsControl sau khi có ref
const fc = this.fullScreenRef.instance.FunctionsControl;
setInputs(this.fullScreenRef, {
title: 'Quy trình phê duyệt',
bodyConfig: {
getComponentOutlet: () =>
from(import('./approval-form.component').then((c) => c.ApprovalFormComponent)),
getDataComponentOutlet: () =>
of({ onClose: () => fc.emitClosePageFullEvent() }),
},
});
this.fullScreenRef.instance.outClose.subscribe(() => {
this.dynamicService.delete(this.fullScreenRef!);
this.fullScreenRef = undefined;
});
this.dynamicService.addToElement(this.fullScreenRef, document.body);
}
ngOnDestroy(): void {
if (this.fullScreenRef) {
this.dynamicService.delete(this.fullScreenRef);
}
}Ví dụ 5: Tùy chỉnh phân vùng header với TemplateRef
import { ChangeDetectionStrategy, Component, TemplateRef, viewChild } from '@angular/core';
import { LibsUiComponentsPagesTemplateFullScreenComponent } from '@libs-ui/components-pages-template-full-screen';
@Component({
selector: 'app-editor',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [LibsUiComponentsPagesTemplateFullScreenComponent],
template: `
<ng-template #customCenter>
<div class="flex items-center gap-[8px]">
<span class="libs-ui-font-h3s libs-ui-color-primary">Soạn thảo nội dung</span>
<span class="libs-ui-badge-status-success">Đang lưu...</span>
</div>
</ng-template>
<libs_ui-components-pages_template_full_screen
[centerTemplate]="customCenter"
[buttonRight]="[{ key: 'button', label: 'Xuất bản', type: 'button-primary' }]"
(outClose)="handlerClose()"
(outClickButton)="handlerClickButton($event)">
<div class="p-[24px]">Nội dung editor...</div>
</libs_ui-components-pages_template_full_screen>
`,
})
export class AppEditorComponent {
handlerClose(): void {
console.log('Đóng editor');
}
handlerClickButton(): void {
console.log('Xuất bản');
}
}@Input() — V1 (libs_ui-components-pages_template_full_screen)
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [title] | string | undefined | Tiêu đề hiển thị ở giữa header, tự động escape HTML | [title]="'Cấu hình chiến dịch'" |
| [labelLeft] | string | 'i18n_back_to_list' | Nhãn nút quay lại bên trái header | [labelLeft]="'Hủy bỏ'" |
| [buttonCenter] | IPagesTemplateFullScreenButton[] | undefined | Danh sách button/switch/label hiển thị ở giữa header (cạnh title) | [buttonCenter]="centerButtons" |
| [buttonRight] | IPagesTemplateFullScreenButton[] | undefined | Danh sách button/switch/label/line-space ở bên phải header | [buttonRight]="rightButtons" |
| [hasEdit] | boolean | false | Hiển thị icon chỉnh sửa cạnh tiêu đề, phát outEdit khi click | [hasEdit]="true" |
| [menuDropDownConfigs] | IDropdown | undefined | Cấu hình menu dropdown "ba chấm dọc" ở góc phải header | [menuDropDownConfigs]="menuConfig" |
| [zIndex] | number | 1000 | CSS z-index của toàn bộ overlay | [zIndex]="9999" |
| [ignoreClosePageFullEvent] | boolean | false | Nếu true, không gửi PostMessage open/close tới app cha | [ignoreClosePageFullEvent]="true" |
| [ignoreBackgroundColor] | boolean | undefined | Nếu true, nền trong suốt thay vì màu mặc định #f2f5f7 | [ignoreBackgroundColor]="true" |
| [disable] | boolean | false | Vô hiệu hóa toàn bộ action trên header (button, switch) | [disable]="isLoading()" |
| [classHeaderInclude] | string | undefined | Class bổ sung cho phần tử header | [classHeaderInclude]="'border-b'" |
| [classBodyInclude] | string | undefined | Class bổ sung cho phần body | [classBodyInclude]="'p-[24px]'" |
| [leftTemplate] | TemplateRef<any> | undefined | Template tùy chỉnh thay thế toàn bộ vùng trái header (bao gồm nút Back) | [leftTemplate]="leftTpl" |
| [centerTemplate] | TemplateRef<any> | undefined | Template tùy chỉnh thay thế vùng giữa header (bao gồm title + buttonCenter) | [centerTemplate]="centerTpl" |
| [templateRight] | TemplateRef<any> | undefined | Template bổ sung vào sau danh sách buttonRight ở vùng phải header | [templateRight]="rightTpl" |
| [divideClassHeader] | { classButtonCancel: string; classButtonCenter: string; classButtonRight: string } | undefined | Override class width cho 3 vùng header (mặc định: 25% / 40% / 35%) | [divideClassHeader]="{ classButtonCancel: 'w-[20%]', classButtonCenter: 'w-[50%]', classButtonRight: 'w-[30%]' }" |
@Input() — V2 (libs_ui-components-pages_template_full_screen_v2)
V2 kế thừa toàn bộ inputs của V1, bổ sung thêm:
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [bodyConfig] | IFullScreenV2BodyConfig | {} | Cấu hình lazy load component vào body. Nếu không truyền getComponentOutlet thì body render bằng ng-content như V1 | [bodyConfig]="bodyConfig" |
@Output()
Cả V1 và V2 đều có chung các outputs sau:
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outClose) | void | Phát ra khi click nút quay lại / đóng ở vùng trái header | handlerClose(): void { /* xử lý đóng */ } | (outClose)="handlerClose()" |
| (outEdit) | void | Phát ra khi click icon edit cạnh tiêu đề (chỉ khi [hasEdit]="true") | handlerEdit(): void { /* mở form chỉnh sửa tiêu đề */ } | (outEdit)="handlerEdit()" |
| (outClickButton) | IPagesTemplateFullScreenButton | Phát ra khi click bất kỳ button nào trong buttonCenter hoặc buttonRight có key: 'button' | handlerClickButton(button: IPagesTemplateFullScreenButton): void { event.stopPropagation(); /* xử lý theo button.label */ } | (outClickButton)="handlerClickButton($event)" |
| (outSelectedMenuDropdown) | IEmitSelectKey \| undefined | Phát ra khi chọn item trong menu dropdown (ba chấm dọc) | handlerMenuSelect(event: IEmitSelectKey \| undefined): void { event.stopPropagation ? event.stopPropagation() : null; /* xử lý theo event.key */ } | (outSelectedMenuDropdown)="handlerMenuSelect($event)" |
FunctionsControl (V2 only)
LibsUiComponentsPagesTemplateFullScreenV2Component expose getter FunctionsControl để component con hoặc service bên ngoài có thể điều khiển trang:
import { ComponentRef } from '@angular/core';
import {
IFunctionControlPageFullScreen,
LibsUiComponentsPagesTemplateFullScreenV2Component,
} from '@libs-ui/components-pages-template-full-screen';
// Lấy FunctionsControl từ ComponentRef
const fc: IFunctionControlPageFullScreen = fullScreenRef.instance.FunctionsControl;
// Gửi sự kiện đóng trang (PostMessage 'close') tới app cha
fc.emitClosePageFullEvent();| Method | Signature | Mô tả |
|---|---|---|
| emitClosePageFullEvent | () => void | Gửi PostMessage với message: 'close' tới ứng dụng cha qua UtilsCommunicateMicro. Thường dùng khi component con muốn chủ động đóng page mà không thông qua (outClose). |
Types & Interfaces
import {
IPagesTemplateFullScreenButton,
IPagesTemplateFullScreenButtonKey,
IFullScreenV2BodyConfig,
IFullScreenV2SectionData,
IFunctionControlPageFullScreen,
} from '@libs-ui/components-pages-template-full-screen';
// Loại button có thể dùng trong buttonCenter và buttonRight
export type IPagesTemplateFullScreenButtonKey =
| 'button' // Nút bấm thông thường
| 'button-status' // Nút hiển thị trạng thái (không click)
| 'switch' // Toggle switch
| 'label' // Label text
| 'line-space' // Đường kẻ phân cách dọc
| 'button-dropdown';// Nút dropdown
// Cấu hình mỗi button trong header
export interface IPagesTemplateFullScreenButton extends IButton {
key: IPagesTemplateFullScreenButtonKey;
classInclude?: string;
disable?: boolean;
// Async callback khi click — dùng cho switch (nhận ISwitchEvent) hoặc button
action?: (event?: ISwitchEvent) => Promise<void>;
configButtonStatus?: IButtonStatus; // Chỉ dùng khi key = 'button-status'
active?: boolean; // Trạng thái ban đầu của switch
labelConfig?: ILabel; // Chỉ dùng khi key = 'label'
}
// Cấu hình body cho V2
export interface IFullScreenV2BodyConfig {
// Hàm trả về Observable<Type<any>> — lazy load component vào body
getComponentOutlet?: TYPE_FUNCTION<any>;
// Hàm trả về Observable<Record> — truyền inputs vào component được load
// Nhận sectionData (chứa { disable: boolean }) làm tham số
getDataComponentOutlet?: TYPE_FUNCTION<Record<string, unknown>>;
// Custom skeleton (bắt buộc là WritableSignal để reactive)
skeletonConfig?: WritableSignal<ISkeletonConfig>;
// Class bổ sung vào wrapper của vùng body
classInclude?: string;
}
// Data được truyền tự động vào component outlet (qua sectionData)
export interface IFullScreenV2SectionData {
disable: boolean;
}
// API điều khiển nội bộ
export interface IFunctionControlPageFullScreen {
emitClosePageFullEvent: () => void;
}Lưu ý quan trọng
⚠️ V1 đã deprecated: LibsUiComponentsPagesTemplateFullScreenComponent có annotation @deprecated. Chỉ dùng để maintain code cũ. Code mới bắt buộc dùng V2.
⚠️ PostMessage lifecycle: Mặc định cả V1 và V2 đều gửi PostMessage với message: 'open' khi ngOnInit và message: 'close' khi ngOnDestroy tới ứng dụng cha. Nếu không cần hành vi này (ví dụ: dùng trong iframe độc lập hoặc demo), hãy set [ignoreClosePageFullEvent]="true".
⚠️ Ping định kỳ 2 giây: Nếu IGNORE_INTERVAL_UPDATE_TIME_LIVE_EVENT_MODAL là false, component gửi ping PostMessage mỗi 2 giây để báo cho app cha rằng user đang hoạt động. V2 dùng takeUntilDestroyed nên tự cleanup; V1 dùng Subject + takeUntil.
⚠️ ComponentRef phải là class property: Khi dùng dynamicService.resolveComponentFactory(), BẮT BUỘC lưu kết quả vào biến class (không phải const trong hàm) để có thể delete khi component cha bị destroy. Quên xóa sẽ gây memory leak.
⚠️ skeletonConfig phải là WritableSignal: Trường bodyConfig.skeletonConfig bắt buộc là signal<ISkeletonConfig>(...) (không phải plain object) để skeleton cập nhật reactive.
⚠️ getDataComponentOutlet nhận sectionData: Hàm getDataComponentOutlet nhận tham số { item: IFullScreenV2SectionData } (wrapper) — truy cập sectionData.item.disable để lấy trạng thái disable. Xem ví dụ 3 ở trên.
⚠️ Body layout: Khi không truyền bodyConfig.getComponentOutlet, V2 không render gì ở body (không có ng-content như V1). Nếu cần ng-content, dùng V1 hoặc luôn truyền getComponentOutlet.
Demo
npx nx serve core-uiTruy cập:
- V1:
http://localhost:4500/page-template/full-screen - V2:
http://localhost:4500/page-template/full-screen-v2
