@libs-ui/components-list
v0.2.357-4
Published
> Component danh sách đa năng hỗ trợ 5 kiểu template (text, radio, checkbox, tag, group/tree) với tìm kiếm, lazy-loading HTTP và Angular Signals.
Downloads
3,657
Readme
@libs-ui/components-list
Component danh sách đa năng hỗ trợ 5 kiểu template (text, radio, checkbox, tag, group/tree) với tìm kiếm, lazy-loading HTTP và Angular Signals.
Giới thiệu
LibsUiComponentsListComponent là một standalone Angular component được thiết kế để xử lý các bài toán hiển thị danh sách phức tạp. Component tích hợp sẵn tìm kiếm online/offline, lazy-loading qua HTTP, virtual scroll và quản lý trạng thái chọn item (single/multi). Mỗi kiểu hiển thị (text, radio, checkbox, tag, group/tree) được khởi tạo động thông qua LibsUiDynamicComponentService, giúp bundle size tối ưu và cấu hình linh hoạt.
Tính năng
- ✅ Đa dạng template: Hỗ trợ 5 loại hiển thị:
text,radio,checkbox,tag,group/tree. - ✅ Cấu trúc cây (Group/Tree): Phân cấp không giới hạn với logic quan hệ cha-con tích hợp sẵn qua
buildListGroupConfig. - ✅ Tìm kiếm thông minh: Hỗ trợ cả tìm kiếm online (server-side) và offline (client-side).
- ✅ Tích hợp HTTP: Tự động load dữ liệu thông qua cấu hình
httpRequestData. - ✅ Virtual Scroll: Tối ưu render danh sách lớn.
- ✅ Custom Row/Column: Tùy biến layout từng item qua cấu hình
rows/cols. - ✅ FunctionControl API: Điều khiển component từ bên ngoài (refresh, resetKeySelected, removeItems, updateData).
- ✅ Validation: Hỗ trợ bắt buộc chọn item qua
validRequired. - ✅ OnPush + Standalone: Hiệu năng cao, dễ import.
Khi nào sử dụng
- Khi cần danh sách tùy chọn đơn (single-select) hoặc đa (multi-select).
- Khi xây dựng bộ lọc (filter) phân cấp dạng cây (tree structure).
- Khi danh sách có kích thước lớn, cần lazy-loading hoặc tìm kiếm online từ API.
- Khi cần hiển thị dữ liệu danh sách với layout item phức tạp (nhiều dòng, nhiều cột).
- Khi xây dựng dropdown, menu chọn nhanh với icon và hình ảnh.
Cài đặt
npm install @libs-ui/components-listImport
import { LibsUiComponentsListComponent } from '@libs-ui/components-list';
import { LibsUiComponentsListTemplatesNoDataComponent } from '@libs-ui/components-list';
// Types & Interfaces
import {
IListConfigItem,
IListConfigItemText,
IListConfigItemRadio,
IListConfigItemCheckbox,
IListConfigItemGroup,
IListConfigItemTag,
IListFunctionControlEvent,
IListDataEmitKey,
IListDataEmitMultiKey,
IDataUpdateToStore,
TYPE_TEMPLATE,
} from '@libs-ui/components-list';
// Helper function để tạo cấu hình group/tree
import { buildListGroupConfig } from '@libs-ui/components-list';
@Component({
standalone: true,
imports: [LibsUiComponentsListComponent],
// ...
})
export class YourComponent {}Ví dụ sử dụng
Ví dụ 1 — Text (Single Select)
<libs_ui-components-list
[config]="configText()"
[keySelected]="keySelected()"
(outSelectKey)="handlerSelectKey($event)"
(outFunctionsControl)="handlerFunctionsControl($event)">
</libs_ui-components-list>import { signal } from '@angular/core';
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitKey, IListFunctionControlEvent } from '@libs-ui/components-list';
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
// Dữ liệu mẫu
const sampleData = [
{ id: '1', name: 'Nguyễn Văn An', avatar: '' },
{ id: '2', name: 'Trần Thị Bình', avatar: '' },
{ id: '3', name: 'Lê Quốc Cường', avatar: '' },
];
export class YourComponent {
protected keySelected = signal<string | undefined>('1');
private listFunctionControl: IListFunctionControlEvent | undefined;
protected configText = signal<IListConfigItem>({
type: 'text',
httpRequestData: signal<IHttpRequestConfig>({
objectInstance: returnListObject(sampleData),
argumentsValue: [],
functionName: 'list',
}),
configTemplateText: signal({
fieldKey: 'id',
getValue: (item) => item.name,
}),
});
protected handlerSelectKey(event?: IListDataEmitKey) {
event?.event?.stopPropagation?.();
this.keySelected.set(event?.key as string);
}
protected handlerFunctionsControl(event: IListFunctionControlEvent) {
this.listFunctionControl = event;
}
}Ví dụ 2 — Checkbox (Multi Select)
<libs_ui-components-list
[config]="configCheckbox()"
[multiKeySelected]="multiKeySelected()"
[searchConfig]="{ placeholder: 'Tìm kiếm...' }"
(outSelectMultiKey)="handlerSelectMultiKey($event)"
(outFunctionsControl)="handlerFunctionsControl($event)">
</libs_ui-components-list>import { signal } from '@angular/core';
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitMultiKey, IListFunctionControlEvent } from '@libs-ui/components-list';
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
const sampleData = [
{ id: '1', name: 'Báo cáo doanh thu' },
{ id: '2', name: 'Báo cáo chi phí' },
{ id: '3', name: 'Báo cáo lợi nhuận' },
];
export class YourComponent {
protected multiKeySelected = signal<string[]>(['1', '2']);
private listFunctionControl: IListFunctionControlEvent | undefined;
protected configCheckbox = signal<IListConfigItem>({
type: 'checkbox',
httpRequestData: signal<IHttpRequestConfig>({
objectInstance: returnListObject(sampleData),
argumentsValue: [],
functionName: 'list',
}),
configTemplateCheckbox: signal({
fieldKey: 'id',
getValue: (item) => item.name,
}),
});
protected handlerSelectMultiKey(event?: IListDataEmitMultiKey) {
this.multiKeySelected.set(event?.keys as string[]);
}
protected handlerFunctionsControl(event: IListFunctionControlEvent) {
this.listFunctionControl = event;
}
}Ví dụ 3 — Group/Tree với buildListGroupConfig
<libs_ui-components-list
[config]="configGroup()"
[multiKeySelected]="multiKeySelected()"
(outSelectMultiKey)="handlerSelectMultiKey($event)">
</libs_ui-components-list>import { signal } from '@angular/core';
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitMultiKey, buildListGroupConfig } from '@libs-ui/components-list';
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
const groupData = [
{
id: 'g1',
name: 'Nhóm A',
items: [
{ id: 'a1', name: 'Item A1' },
{ id: 'a2', name: 'Item A2' },
],
},
{
id: 'g2',
name: 'Nhóm B',
items: [
{ id: 'b1', name: 'Item B1' },
{ id: 'b2', name: 'Item B2' },
],
},
];
export class YourComponent {
protected multiKeySelected = signal<string[]>([]);
// buildListGroupConfig tạo sẵn cấu hình cho các pattern phổ biến
protected configGroup = signal<IListConfigItem>({
type: 'group',
httpRequestData: signal<IHttpRequestConfig>({
objectInstance: returnListObject(groupData),
argumentsValue: [],
functionName: 'list',
}),
configTemplateGroup: buildListGroupConfig('group_tree_checkbox_1'),
});
protected handlerSelectMultiKey(event?: IListDataEmitMultiKey) {
this.multiKeySelected.set(event?.keys as string[]);
}
}Ví dụ 4 — Radio
<libs_ui-components-list
[config]="configRadio()"
[keySelected]="keySelected()"
(outSelectKey)="handlerSelectKey($event)">
</libs_ui-components-list>import { signal } from '@angular/core';
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitKey } from '@libs-ui/components-list';
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
const sampleData = [
{ id: 'all', name: 'Tất cả' },
{ id: 'active', name: 'Đang hoạt động' },
{ id: 'inactive', name: 'Ngừng hoạt động' },
];
export class YourComponent {
protected keySelected = signal<string | undefined>('all');
protected configRadio = signal<IListConfigItem>({
type: 'radio',
httpRequestData: signal<IHttpRequestConfig>({
objectInstance: returnListObject(sampleData),
argumentsValue: [],
functionName: 'list',
}),
configTemplateRadio: signal({
fieldKey: 'id',
getValue: (item) => item.name,
}),
});
protected handlerSelectKey(event?: IListDataEmitKey) {
this.keySelected.set(event?.key as string);
}
}Ví dụ 5 — Tag
<libs_ui-components-list
[config]="configTag()"
[searchConfig]="{ placeholder: 'Tìm theo tên...' }"
[multiKeySelected]="multiKeySelected()"
(outSelectMultiKey)="handlerSelectMultiKey($event)">
</libs_ui-components-list>import { signal } from '@angular/core';
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitMultiKey } from '@libs-ui/components-list';
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
const tagData = [
{ id: '1', name: 'Angular' },
{ id: '2', name: 'TypeScript' },
{ id: '3', name: 'RxJS' },
{ id: '4', name: 'Signals' },
];
export class YourComponent {
protected multiKeySelected = signal<string[]>([]);
protected configTag = signal<IListConfigItem>({
type: 'tag',
httpRequestData: signal<IHttpRequestConfig>({
objectInstance: returnListObject(tagData),
argumentsValue: [],
functionName: 'list',
}),
configTemplateTag: signal({
fieldKey: 'id',
getValue: (item) => item.name,
}),
});
protected handlerSelectMultiKey(event?: IListDataEmitMultiKey) {
this.multiKeySelected.set(event?.keys as string[]);
}
}Ví dụ 6 — Custom Row & Column
<libs_ui-components-list [config]="configCustomRow()">
</libs_ui-components-list>import { signal } from '@angular/core';
import { of } from 'rxjs';
import { LibsUiComponentsListComponent, IListConfigItem } from '@libs-ui/components-list';
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
import { get } from '@libs-ui/utils';
const richData = [
{ id: '1', name: 'Nguyễn Văn An', subTitle: 'Trưởng phòng', desc: 'Kinh nghiệm 5 năm' },
{ id: '2', name: 'Trần Thị Bình', subTitle: 'Nhân viên', desc: 'Kinh nghiệm 2 năm' },
];
export class YourComponent {
protected configCustomRow = signal<IListConfigItem>({
type: 'text',
httpRequestData: signal<IHttpRequestConfig>({
objectInstance: returnListObject(richData),
argumentsValue: [],
functionName: 'list',
}),
configTemplateText: signal({
fieldKey: 'id',
rows: signal([
signal({
classRow: 'flex flex-col mb-1',
cols: signal([
signal({ getValue: (data) => of(get(data, 'item.name') as string) }),
signal({ getValue: (data) => of(get(data, 'item.subTitle') as string) }),
]),
}),
signal({
classRow: 'text-sm text-gray-500 mt-1',
getValue: (data) => of(get(data, 'item.desc') as string),
}),
]),
}),
});
}Ví dụ 7 — FunctionControl: refresh, resetKeySelected, removeItems
import { signal } from '@angular/core';
import { LibsUiComponentsListComponent, IListFunctionControlEvent } from '@libs-ui/components-list';
export class YourComponent {
private listFunctionControl: IListFunctionControlEvent | undefined;
protected handlerFunctionsControl(event: IListFunctionControlEvent) {
this.listFunctionControl = event;
}
protected handlerRefreshList() {
this.listFunctionControl?.refresh();
}
protected handlerResetSelection() {
this.listFunctionControl?.resetKeySelected();
}
protected handlerRemoveItems(keys: string[]) {
this.listFunctionControl?.removeItems(keys);
}
protected async handlerValidate(): Promise<boolean> {
return this.listFunctionControl?.checkIsValid() ?? true;
}
}Ví dụ 8 — Saved Views (Group flat với single-select item)
<libs_ui-components-list
[config]="configSavedViews()"
[keySelected]="savedViewKeySelected()"
(outSelectKey)="handlerSelectedSavedView($event)">
</libs_ui-components-list>import { signal } from '@angular/core';
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitKey } from '@libs-ui/components-list';
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
const savedViewGroups = [
{
key: 'my_views',
label: 'Views của tôi',
views: [
{ key: 'view_high_value', label: 'Khách hàng giá trị cao' },
{ key: 'view_new', label: 'Khách hàng mới' },
],
},
{
key: 'shared_views',
label: 'Views chia sẻ',
views: [
{ key: 'view_vip', label: 'VIP' },
],
},
];
export class YourComponent {
protected savedViewKeySelected = signal<string[] | undefined>(['view_high_value']);
protected configSavedViews = signal<IListConfigItem>({
type: 'group',
httpRequestData: signal<IHttpRequestConfig>({
objectInstance: returnListObject(savedViewGroups),
argumentsValue: [],
functionName: 'list',
}),
configTemplateGroup: signal({
fieldKey: 'key',
fieldGetItems: 'views',
getLabelGroup: (group) => `${group.label} (${group.views.length})`,
getMaxLevelGroup: () => 2,
getLabelItem: (item) => item.label,
singleSelectItem: true,
hasIconCheckSingleSelectItem: true,
hasBackgroundColorWhenItemSelected: true,
colorBlueWhenItemSelected: true,
iconExpand: 'right',
ignoreCheckboxGroup: true,
ignoreCheckboxItem: true,
getExpandGroup: () => true,
}),
});
protected handlerSelectedSavedView(event?: IListDataEmitKey) {
this.savedViewKeySelected.set(event ? [event.key as string] : undefined);
}
}@Input()
libs_ui-components-list
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| autoSelectedFirstItemCallOutsideBefore | boolean | false | Tự động chọn item đầu tiên nếu chưa có item nào được chọn khi component khởi tạo. | [autoSelectedFirstItemCallOutsideBefore]="true" |
| backgroundListCustom | string | 'bg-[#ffffff]' | Class màu nền tùy chỉnh cho container danh sách. | [backgroundListCustom]="'bg-gray-50'" |
| buttonsOther | IButton[] \| undefined | undefined | Danh sách các nút bấm bổ sung hiển thị phía trên danh sách. | [buttonsOther]="extraButtons" |
| clickExactly | boolean | undefined | Chỉ kích hoạt sự kiện chọn khi click trực tiếp vào vùng item, không kích hoạt khi click vào popover hoặc vùng ngoài. | [clickExactly]="true" |
| config | IListConfigItem | BẮT BUỘC | Cấu hình chính cho component: loại template, nguồn dữ liệu, config hiển thị item. | [config]="configText()" |
| disable | boolean | undefined | Vô hiệu hóa toàn bộ component, không cho phép chọn item. | [disable]="isDisabled()" |
| disableLabel | boolean | undefined | Vô hiệu hóa phần label tiêu đề của danh sách. | [disableLabel]="true" |
| dividerClassInclude | string | undefined | Class CSS bổ sung cho đường kẻ phân cách giữa các phần. | [dividerClassInclude]="'border-blue-200'" |
| dropdownTabKeyActive | string | undefined | Key của tab đang active trong dropdown. Thay đổi giá trị này sẽ trigger refresh danh sách. | [dropdownTabKeyActive]="activeTab()" |
| focusInputSearch | boolean | undefined | Tự động focus vào ô tìm kiếm sau khi dữ liệu load xong. | [focusInputSearch]="true" |
| functionGetItemsAutoAddList | (() => Array<any>) \| undefined | undefined | Hàm trả về danh sách item được thêm tự động vào đầu danh sách (ví dụ: option "Tất cả"). | [functionGetItemsAutoAddList]="getExtraItems" |
| hasButtonUnSelectOption | boolean | undefined | Hiển thị nút "Bỏ chọn" để xóa toàn bộ lựa chọn hiện tại. | [hasButtonUnSelectOption]="true" |
| hasDivider | boolean | true | Hiển thị đường kẻ phân cách giữa các phần (search bar và danh sách). | [hasDivider]="false" |
| hiddenInputSearch | boolean | false | Ẩn thanh tìm kiếm. Mặc định hiển thị. | [hiddenInputSearch]="true" |
| ignoreClassDisableDefaultWhenUseKeysDisableItem | boolean | undefined | Bỏ class CSS disable mặc định khi dùng keysDisableItem, cho phép tự xử lý UI disabled từng phần trong rows. | [ignoreClassDisableDefaultWhenUseKeysDisableItem]="true" |
| isSearchOnline | boolean | undefined | Bật chế độ tìm kiếm từ server (call API với keyword). Mặc định là tìm kiếm client-side. | [isSearchOnline]="true" |
| keySearch | string | undefined | Giá trị keyword tìm kiếm được truyền vào từ bên ngoài. | [keySearch]="searchKeyword()" |
| keySelected | unknown | undefined | Key của item đang được chọn (single select). Dùng cho type text và radio. | [keySelected]="selectedId()" |
| keysDisableItem | Array<unknown> \| undefined | undefined | Danh sách các key bị vô hiệu hóa, không thể chọn. Không dùng kết hợp với configCheckboxCheckAll. | [keysDisableItem]="disabledIds()" |
| keysHiddenItem | Array<unknown> \| undefined | undefined | Danh sách các key bị ẩn khỏi danh sách. Không dùng kết hợp với configCheckboxCheckAll. | [keysHiddenItem]="hiddenIds()" |
| labelConfig | ILabel | undefined | Cấu hình label tiêu đề hiển thị phía trên danh sách. | [labelConfig]="{ label: 'Chọn danh mục' }" |
| loadingIconSize | 'large' \| 'medium' \| 'small' \| 'smaller' \| undefined | undefined | Kích thước icon loading khi đang tải dữ liệu. | [loadingIconSize]="'small'" |
| maxItemShow | number | undefined | Số lượng item tối đa hiển thị trong viewport trước khi bắt đầu scroll. | [maxItemShow]="5" |
| multiKeySelected | Array<unknown> \| undefined | undefined | Danh sách các key đang được chọn (multi select). Dùng cho type checkbox và group. | [multiKeySelected]="selectedIds()" |
| paddingLeftItem | boolean | undefined | Thêm padding bên trái cho các item trong danh sách. | [paddingLeftItem]="true" |
| resetKeyWhenSelectAllKeyDropdown | boolean | undefined | Reset key đã chọn khi người dùng chọn option "Tất cả" trong dropdown. | [resetKeyWhenSelectAllKeyDropdown]="true" |
| searchConfig | IInputSearchConfig | {} | Cấu hình chi tiết cho ô tìm kiếm (placeholder, debounce, v.v.). | [searchConfig]="{ placeholder: 'Tìm kiếm...' }" |
| searchPadding | boolean | undefined | Thêm padding cho container thanh tìm kiếm. | [searchPadding]="true" |
| showValidateBottom | boolean | undefined | Hiển thị thông báo lỗi validation ở phía dưới danh sách thay vì phía trên. | [showValidateBottom]="true" |
| skipFocusInputWhenKeySearchStoreUndefined | boolean | undefined | Bỏ qua auto-focus vào ô tìm kiếm khi keySearchStore đang là undefined. | [skipFocusInputWhenKeySearchStoreUndefined]="true" |
| templateRefNotSearchNoData | TemplateRef \| undefined | undefined | Template tùy chỉnh hiển thị khi chưa có từ khóa tìm kiếm và danh sách rỗng. | [templateRefNotSearchNoData]="tplNoData" |
| templateRefSearchNoData | TemplateRef \| undefined | undefined | Template tùy chỉnh hiển thị khi tìm kiếm không có kết quả. | [templateRefSearchNoData]="tplNoResult" |
| validRequired | IValidRequired \| undefined | undefined | Cấu hình bắt buộc phải chọn ít nhất một item. Tích hợp với form validation. | [validRequired]="{ message: 'Vui lòng chọn ít nhất 1 mục' }" |
| zIndex | number | undefined | Z-index CSS cho container danh sách, dùng khi cần quản lý lớp hiển thị. | [zIndex]="100" |
@Output()
libs_ui-components-list
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outChangeView) | Array<any> | Phát ra mảng item đang hiển thị sau mỗi lần danh sách thay đổi (load xong, filter, search). | handlerChangeView(items: any[]): void { items.stopPropagation?.(); ... } | (outChangeView)="handlerChangeView($event)" |
| (outChangStageFlagMousePopover) | IFlagMouse | Phát ra trạng thái hover chuột vào/ra khỏi popover trong danh sách. | handlerFlagMousePopover(flag: IFlagMouse): void { flag.stopPropagation?.(); ... } | (outChangStageFlagMousePopover)="handlerFlagMousePopover($event)" |
| (outClickButtonOther) | IButton | Phát ra khi người dùng click vào một trong các nút bổ sung (buttonsOther). | handlerClickButtonOther(btn: IButton): void { btn.stopPropagation?.(); ... } | (outClickButtonOther)="handlerClickButtonOther($event)" |
| (outFieldKey) | string \| undefined | Phát ra tên trường fieldKey đang sử dụng trong template hiện tại. | handlerFieldKey(key: string \| undefined): void { ... } | (outFieldKey)="handlerFieldKey($event)" |
| (outFunctionsControl) | IListFunctionControlEvent | Phát ra object chứa các method điều khiển component từ bên ngoài. Nên lưu lại để gọi sau. | handlerFunctionsControl(event: IListFunctionControlEvent): void { this.listControl = event; } | (outFunctionsControl)="handlerFunctionsControl($event)" |
| (outKeySearch) | string | Phát ra keyword mỗi khi người dùng thay đổi nội dung ô tìm kiếm. | handlerKeySearch(keyword: string): void { this.keyword.set(keyword); } | (outKeySearch)="handlerKeySearch($event)" |
| (outLoadItemsComplete) | { items: Array<any>; paging?: IPaging } | Phát ra sau khi HTTP request tải dữ liệu hoàn tất. | handlerLoadComplete(event: { items: any[]; paging?: IPaging }): void { this.total.set(event.paging?.total ?? 0); } | (outLoadItemsComplete)="handlerLoadComplete($event)" |
| (outLoading) | boolean | Phát ra trạng thái loading: true khi đang tải, false khi xong. | handlerLoading(loading: boolean): void { this.isLoading.set(loading); } | (outLoading)="handlerLoading($event)" |
| (outSelectKey) | IListDataEmitKey \| undefined | Phát ra khi người dùng chọn 1 item (single select, type text/radio). undefined khi bỏ chọn. | handlerSelectKey(event?: IListDataEmitKey): void { event?.event?.stopPropagation?.(); this.selected.set(event?.key as string); } | (outSelectKey)="handlerSelectKey($event)" |
| (outSelectMultiKey) | IListDataEmitMultiKey \| undefined | Phát ra khi người dùng chọn/bỏ chọn item (multi select, type checkbox/group). | handlerSelectMultiKey(event?: IListDataEmitMultiKey): void { this.selectedIds.set(event?.keys as string[]); } | (outSelectMultiKey)="handlerSelectMultiKey($event)" |
| (outUnSelectMultiKey) | Array<unknown> | Phát ra mảng keys bị bỏ chọn trong multi select. | handlerUnSelectMultiKey(keys: unknown[]): void { ... } | (outUnSelectMultiKey)="handlerUnSelectMultiKey($event)" |
FunctionControl (via outFunctionsControl)
Nhận IListFunctionControlEvent qua (outFunctionsControl) để điều khiển component từ bên ngoài:
| Method | Signature | Mô tả |
|---|---|---|
| checkIsValid | () => Promise<boolean> | Kiểm tra tính hợp lệ của lựa chọn khi có cấu hình validRequired. Trả về true nếu hợp lệ. |
| getRectListView | () => Promise<IBoundingClientRect> | Lấy kích thước và vị trí DOM của container danh sách. |
| refresh | () => Promise<void> | Làm mới danh sách: clone lại config và tải lại dữ liệu từ đầu. |
| removeItems | (keys: string[]) => Promise<void> | Xóa các item khỏi store hiển thị theo danh sách keys. |
| resetKeySelected | () => Promise<void> | Xóa toàn bộ item đang được chọn (cả single lẫn multi select). |
| updateData | (data: IDataUpdateToStore) => Promise<void> | Cập nhật/merge dữ liệu vào store hiện tại mà không cần reload từ API. |
// Sử dụng FunctionControl
export class YourComponent {
private listControl: IListFunctionControlEvent | undefined;
protected handlerFunctionsControl(event: IListFunctionControlEvent) {
this.listControl = event;
}
protected handlerRefresh() {
this.listControl?.refresh();
}
protected async handlerSubmit(): Promise<void> {
const isValid = await this.listControl?.checkIsValid();
if (!isValid) {
return;
}
// proceed...
}
}Sub-component: libs_ui-components-list-templates-no_data
Component hiển thị trạng thái no-data, có thể dùng độc lập hoặc truyền qua [templateRefSearchNoData] / [templateRefNotSearchNoData].
<!-- No data cơ bản -->
<libs_ui-components-list-templates-no_data
[config]="configText()"
[keySearch]="''"
[loading]="false"
[enableNoData]="true">
</libs_ui-components-list-templates-no_data>
<!-- Custom template khi chưa search -->
<ng-template #tplNotSearch>
<div class="flex flex-col items-center p-4">
<span>Vui lòng nhập thông tin để tìm kiếm.</span>
</div>
</ng-template>
<libs_ui-components-list-templates-no_data
[config]="configText()"
[keySearch]="''"
[loading]="false"
[enableNoData]="true"
[templateRefNotSearchNoData]="tplNotSearch">
</libs_ui-components-list-templates-no_data>
<!-- Custom template khi search không có kết quả -->
<ng-template #tplNoResult let-keySearch="keySearch">
<div class="p-4 text-center text-gray-500">
Không tìm thấy kết quả cho: <b>{{ keySearch }}</b>
</div>
</ng-template>
<libs_ui-components-list-templates-no_data
[config]="configText()"
[keySearch]="'từ khóa tìm kiếm'"
[loading]="false"
[enableNoData]="true"
[templateRefSearchNoData]="tplNoResult">
</libs_ui-components-list-templates-no_data>@Input() của libs_ui-components-list-templates-no_data
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| config | IListConfigItem \| undefined | undefined | Cấu hình list để lấy text no-data tùy chỉnh (textNoData). | [config]="configText()" |
| enableNoData | boolean | BẮT BUỘC | Bật/tắt hiển thị trạng thái no-data. Phải là true để component hiển thị. | [enableNoData]="true" |
| keySearch | string | BẮT BUỘC | Keyword tìm kiếm hiện tại. Rỗng → hiển thị no-data; có giá trị → hiển thị no-result. | [keySearch]="keyword()" |
| loading | boolean | BẮT BUỘC | Trạng thái đang tải. Khi true không hiển thị no-data. | [loading]="isLoading()" |
| templateRefNotSearchNoData | TemplateRef \| undefined | undefined | Template tùy chỉnh khi chưa có từ khóa tìm kiếm (keySearch rỗng). | [templateRefNotSearchNoData]="tplNotSearch" |
| templateRefSearchNoData | TemplateRef \| undefined | undefined | Template tùy chỉnh khi tìm kiếm không có kết quả. Context: { keySearch: string }. | [templateRefSearchNoData]="tplNoResult" |
Helper: buildListGroupConfig
Hàm tiện ích tạo sẵn cấu hình IListConfigItemGroup theo các pattern phổ biến:
import { buildListGroupConfig } from '@libs-ui/components-list';
// Tham số 1: template name
// Tham số 2: overrides (tùy chọn) — ghi đè fieldKey, fieldGetItems, getLabelGroup, getLabelItem, getMaxLevelGroup
const groupConfig = buildListGroupConfig('group_tree_checkbox_1', {
fieldKey: 'code',
fieldGetItems: 'children',
getLabelGroup: (g) => g.categoryName,
getLabelItem: (item) => item.productName,
});| Template | Mô tả |
|---|---|
| group_tree_checkbox_1 | Tree nhiều cấp, checkbox, logic Bank: chọn con → chọn cha; bỏ cha → bỏ hết con. |
| group_tree_checkbox_2 | Tree nhiều cấp, checkbox, logic Retail: chọn cha → chọn hết con; bỏ cha không bỏ con. |
| group_tree_radio_1 | Tree nhiều cấp, radio, single-select theo logic child triggers parent. |
| group_tree_radio_2 | Tree nhiều cấp, radio, single-select (biến thể emit all item checked). |
| group_tree_text_1 | Tree nhiều cấp, text, single-select theo logic Bank. |
| group_tree_text_2 | Tree nhiều cấp, text, single-select theo logic Retail. |
| group_label_checkbox_1 | Group dạng label phân nhóm, item checkbox multi-select. |
| group_label_text_1 | Group dạng label phân nhóm, item text single-select với icon check. |
Types & Interfaces
import {
TYPE_TEMPLATE,
IListConfigItem,
IListConfigItemText,
IListConfigItemRadio,
IListConfigItemCheckbox,
IListConfigItemGroup,
IListConfigItemTag,
IListConfigItemTextRow,
IListConfigItemTextCol,
IListDataEmitKey,
IListDataEmitMultiKey,
IListFunctionControlEvent,
IDataUpdateToStore,
IDataFunctionCallInConfig,
IConfigDescriptionGroup,
CONFIG_LIST_GROUP_TYPE,
} from '@libs-ui/components-list';
// Kiểu template
type TYPE_TEMPLATE = 'checkbox' | 'group' | 'radio' | 'text' | 'tag';
// Cấu hình chính
interface IListConfigItem {
type: TYPE_TEMPLATE;
httpRequestData?: WritableSignal<IHttpRequestConfig>;
configTemplateText?: WritableSignal<IListConfigItemText>;
configTemplateRadio?: WritableSignal<IListConfigItemRadio>;
configTemplateCheckbox?: WritableSignal<IListConfigItemCheckbox>;
configTemplateGroup?: WritableSignal<IListConfigItemGroup>;
configTemplateTag?: WritableSignal<IListConfigItemTag>;
textNoData?: string; // Text hiển thị khi không có dữ liệu
textNoDataClassInclude?: string; // Class CSS cho text no-data
textSearchNoData?: string; // Text khi tìm kiếm không có kết quả
autoSelectFirstItem?: boolean; // Tự động chọn item đầu tiên khi có dữ liệu
sort?: (items: Array<any>) => void; // Hàm sort tùy chỉnh
hasIconNoData?: boolean;
ignoreShowDataWhenNotSearch?: boolean; // Ẩn danh sách khi chưa search
highlightTextSearchInResult?: boolean; // Highlight keyword trong kết quả
heightItem?: number;
minHeightItem?: number;
}
// Event khi chọn single item
interface IListDataEmitKey {
key: unknown; // Key của item được chọn
item: any; // Toàn bộ object item
isClickManual: boolean; // true nếu do user click, false nếu programmatic
}
// Event khi chọn nhiều item
interface IListDataEmitMultiKey {
keys: Array<unknown>; // Mảng keys được chọn
mapKeys: Array<IListDataEmitKey>; // Chi tiết từng item được chọn
isClickManual: boolean;
}
// FunctionControl API
interface IListFunctionControlEvent {
checkIsValid: () => Promise<boolean>;
refresh: () => Promise<void>;
resetKeySelected: () => Promise<void>;
getRectListView: () => Promise<IBoundingClientRect>;
removeItems: (keys: Array<string>) => Promise<void>;
updateData: (data: IDataUpdateToStore) => Promise<void>;
setMessageError?: (message: string) => Promise<void>;
}
// Dùng cho updateData
interface IDataUpdateToStore {
newData: WritableSignal<Array<WritableSignal<any>>>;
functionCustomAddDataToStore: (
newData: WritableSignal<Array<WritableSignal<any>>>,
store: WritableSignal<Array<WritableSignal<any>>>
) => void;
}Lưu ý quan trọng
⚠️ Config bắt buộc: Phải luôn cung cấp type và signal config tương ứng. Ví dụ: type text cần có configTemplateText: signal({...}), type group cần có configTemplateGroup: signal({...}) hoặc dùng buildListGroupConfig(...).
⚠️ WritableSignal cho config con: Tất cả các config con (configTemplateText, configTemplateCheckbox, httpRequestData, v.v.) phải là WritableSignal để component có thể reactive và refresh đúng cách khi gọi FunctionControl.refresh().
⚠️ keysDisableItem không tương thích với configCheckboxCheckAll: Không dùng [keysDisableItem] kết hợp với template checkbox có cấu hình configCheckboxCheckAll vì sẽ gây hành vi không xác định.
⚠️ keysHiddenItem không tương thích với configCheckboxCheckAll: Tương tự như trên, không dùng [keysHiddenItem] kết hợp với configCheckboxCheckAll.
⚠️ Virtual Scroll mặc định: Virtual scroll được bật mặc định cho danh sách lớn. Nếu cần tắt (ví dụ: dùng trong config badge của table), set notUseVirtualScroll: true trong configTemplateText.
⚠️ outFunctionsControl: Luôn lưu lại IListFunctionControlEvent khi nhận từ (outFunctionsControl) để dùng sau. Chỉ gọi các method sau khi component đã mount (ngOnInit xong).
⚠️ dropdownTabKeyActive và refresh: Thay đổi giá trị dropdownTabKeyActive sẽ tự động trigger refresh() bên trong component. Sử dụng cơ chế này khi cần reload danh sách khi đổi tab trong dropdown.
Demo
npx nx serve core-uiTruy cập: http://localhost:4500/components/list
