@libs-ui/components-tags
v0.2.357-4
Published
> Component Angular quản lý và hiển thị danh sách thẻ (tags) với hỗ trợ chọn từ popover list, nhập tự do, tìm kiếm, avatar, validation và tùy chỉnh giao diện.
Readme
@libs-ui/components-tags
Component Angular quản lý và hiển thị danh sách thẻ (tags) với hỗ trợ chọn từ popover list, nhập tự do, tìm kiếm, avatar, validation và tùy chỉnh giao diện.
Giới thiệu
LibsUiComponentsTagsComponent là component linh hoạt cho phép người dùng chọn một hoặc nhiều mục từ danh sách gợi ý (tích hợp libs_ui-components-list và libs_ui-components-popover) hoặc nhập trực tiếp để tạo tag mới. Component hỗ trợ tìm kiếm online/offline, hiển thị avatar bên cạnh nhãn tag, kiểm tra trùng lặp thông minh, các chế độ validation đa dạng và expose FunctionControl để component cha điều khiển từ bên ngoài.
Tính năng
- ✅ Multiple Selection: Chọn nhiều mục đồng thời từ danh sách gợi ý
- ✅ Single Selection: Chế độ chọn một mục duy nhất với giao diện giống dropdown
- ✅ Free Input / Enter to Add: Nhập tự do, nhấn Enter để tạo tag mới
- ✅ Search Online/Offline: Tìm kiếm linh hoạt từ local data hoặc gọi API debounced 1.5s
- ✅ Avatar Support: Hiển thị avatar ảnh hoặc text avatar bên cạnh mỗi tag
- ✅ Duplicate Check: Tự động kiểm tra trùng lặp theo label và key trước khi thêm
- ✅ Validation: Required, max items, và pattern (regex) validation
- ✅ Custom Styling: Tùy chỉnh màu sắc, border cho từng tag qua
ITagsConfig.getStyles - ✅ Disable / Readonly Mode: Hỗ trợ trạng thái vô hiệu hóa và chỉ đọc
- ✅ FunctionControl API: Expose
checkIsValid,clearItemSelected,resetToDefaultDataSelected, v.v. quaoutFunctionsControl - ✅ HTTP Detail Loading: Tự động load chi tiết item theo keys qua
httpRequestDetailItemByIds - ✅ OnPush Change Detection: Tối ưu hiệu năng với Angular Signals
Khi nào sử dụng
- Trong các form cần gán nhãn (labels), người nhận (recipients), danh mục (categories), kỹ năng (skills)
- Khi bộ chọn cần tìm kiếm mạnh, hiển thị thông tin phong phú (avatar, tên) kèm mỗi lựa chọn
- Thay thế
inputtext thuần khi dữ liệu có cấu trúc và cần liên kết với danh sách nguồn - Khi nghiệp vụ cần cho phép tạo tag mới tự do kết hợp với chọn tag có sẵn
- Khi cần chọn đơn với giao diện giống dropdown nhưng muốn hiển thị tag bên trong ô
Cài đặt
npm install @libs-ui/components-tagsImport
import { LibsUiComponentsTagsComponent } from '@libs-ui/components-tags';
import { ITagsConfig, ITagsFunctionControlEvent } from '@libs-ui/components-tags';
@Component({
standalone: true,
imports: [LibsUiComponentsTagsComponent],
})
export class YourComponent {}Ví dụ sử dụng
1. Cơ bản — Chọn nhiều tag từ danh sách
// your.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsTagsComponent, ITagsConfig } from '@libs-ui/components-tags';
import { IListConfigItem } from '@libs-ui/components-list';
import { of } from 'rxjs';
@Component({
standalone: true,
imports: [LibsUiComponentsTagsComponent],
templateUrl: './your.component.html',
})
export class YourComponent {
private readonly tagService = {
getAll: () => of({
data: [
{ id: '1', label: 'Angular' },
{ id: '2', label: 'React' },
{ id: '3', label: 'Vue' },
{ id: '4', label: 'TypeScript' },
],
paging: { page: 1, total_pages: 1 },
}),
};
readonly defaultItems = [{ id: '1', label: 'Angular' }];
readonly config: ITagsConfig = {};
readonly listConfig: IListConfigItem = {
type: 'checkbox',
configTemplateCheckbox: signal({
fieldKey: 'id',
getValue: (item: any) => item.label,
}),
httpRequestData: signal({
objectInstance: this.tagService,
functionName: 'getAll',
argumentsValue: [],
}) as any,
};
protected handlerSelectTags(items: unknown[]): void {
// items là mảng các object đã chọn
}
}<!-- your.component.html -->
<libs_ui-components-tags
[config]="config"
[listConfig]="listConfig"
[defaultItemsSelected]="defaultItems"
placeholder="Tìm kiếm hoặc nhập tag..."
(outSelectMultiItem)="handlerSelectTags($event)"
/>2. Chọn đơn — Giao diện giống Dropdown
// your.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsTagsComponent, ITagsConfig } from '@libs-ui/components-tags';
import { IListConfigItem } from '@libs-ui/components-list';
import { of } from 'rxjs';
@Component({
standalone: true,
imports: [LibsUiComponentsTagsComponent],
templateUrl: './your.component.html',
})
export class YourComponent {
private readonly categoryService = {
getAll: () => of({
data: [
{ id: 'BE', label: 'Backend' },
{ id: 'FE', label: 'Frontend' },
{ id: 'DS', label: 'Design' },
],
paging: { page: 1, total_pages: 1 },
}),
};
readonly config: ITagsConfig = {
uiLikeDropdown: true,
ignoreInput: true,
};
readonly listConfig: IListConfigItem = {
type: 'text',
configTemplateText: signal({
fieldKey: 'id',
getValue: (item: any) => item.label,
}),
httpRequestData: signal({
objectInstance: this.categoryService,
functionName: 'getAll',
argumentsValue: [],
}) as any,
};
protected handlerSelectCategory(items: unknown[]): void {
// items[0] là item được chọn
}
}<!-- your.component.html -->
<libs_ui-components-tags
[config]="config"
[listConfig]="listConfig"
[singleSelected]="true"
placeholder="Chọn danh mục..."
(outSelectMultiItem)="handlerSelectCategory($event)"
/>3. Hiển thị Avatar
// your.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsTagsComponent, ITagsConfig } from '@libs-ui/components-tags';
import { IListConfigItem } from '@libs-ui/components-list';
import { of } from 'rxjs';
@Component({
standalone: true,
imports: [LibsUiComponentsTagsComponent],
templateUrl: './your.component.html',
})
export class YourComponent {
private readonly userService = {
getAll: () => of({
data: [
{ id: '1', label: 'Nguyen Van A', avatar: 'https://example.com/avatar1.png', user: 'nva' },
{ id: '2', label: 'Tran Thi B', avatar: 'https://example.com/avatar2.png', user: 'ttb' },
{ id: '3', label: 'Le Van C', user: 'lvc' },
],
paging: { page: 1, total_pages: 1 },
}),
};
readonly config: ITagsConfig = {};
readonly listConfig: IListConfigItem = {
type: 'text',
configTemplateText: signal({
fieldKey: 'id',
getValue: (item: any) => item.label,
}),
httpRequestData: signal({
objectInstance: this.userService,
functionName: 'getAll',
argumentsValue: [],
}) as any,
};
}<!-- your.component.html -->
<libs_ui-components-tags
[config]="config"
[listConfig]="listConfig"
fieldGetImage="avatar"
fieldGetTextAvatar="user"
[imageSize]="24"
placeholder="Chọn thành viên..."
/>4. Validation — Required và Max Items
// your.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsTagsComponent, ITagsConfig, ITagsFunctionControlEvent } from '@libs-ui/components-tags';
import { IListConfigItem } from '@libs-ui/components-list';
import { of } from 'rxjs';
@Component({
standalone: true,
imports: [LibsUiComponentsTagsComponent],
templateUrl: './your.component.html',
})
export class YourComponent {
private readonly tagService = {
getAll: () => of({ data: [{ id: '1', label: 'Angular' }, { id: '2', label: 'React' }], paging: { page: 1, total_pages: 1 } }),
};
readonly config: ITagsConfig = {};
readonly listConfig: IListConfigItem = {
type: 'checkbox',
configTemplateCheckbox: signal({ fieldKey: 'id', getValue: (item: any) => item.label }),
httpRequestData: signal({ objectInstance: this.tagService, functionName: 'getAll', argumentsValue: [] }) as any,
};
private tagsControl: ITagsFunctionControlEvent | undefined;
protected handlerFunctionsControl(event: ITagsFunctionControlEvent): void {
event.stopPropagation();
this.tagsControl = event;
}
protected handlerValidEvent(isValid: boolean): void {
event?.stopPropagation?.();
// xử lý trạng thái form
}
async submitForm(): Promise<void> {
const isValid = await this.tagsControl?.checkIsValid();
if (!isValid) return;
// tiếp tục submit
}
}<!-- your.component.html -->
<libs_ui-components-tags
[config]="config"
[listConfig]="listConfig"
[validRequired]="{ message: 'Vui lòng chọn ít nhất một tag' }"
[validMaxItemSelected]="{ value: 3, message: 'Chỉ được chọn tối đa 3 tag' }"
(outFunctionsControl)="handlerFunctionsControl($event)"
(outValidEvent)="handlerValidEvent($event)"
/>5. Tùy chỉnh màu sắc tag
// your.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsTagsComponent, ITagsConfig } from '@libs-ui/components-tags';
import { IListConfigItem } from '@libs-ui/components-list';
import { of } from 'rxjs';
@Component({
standalone: true,
imports: [LibsUiComponentsTagsComponent],
templateUrl: './your.component.html',
})
export class YourComponent {
private readonly tagService = {
getAll: () => of({
data: [{ id: '1', label: 'Cao', priority: 'high' }, { id: '2', label: 'Trung bình', priority: 'medium' }, { id: '3', label: 'Thấp', priority: 'low' }],
paging: { page: 1, total_pages: 1 },
}),
};
readonly config: ITagsConfig = {
getStyles: (item: any): Record<string, string> => {
const map: Record<string, Record<string, string>> = {
high: { backgroundColor: '#fee2e2', color: '#b91c1c', border: '1px solid #f87171' },
medium: { backgroundColor: '#fef9c3', color: '#a16207', border: '1px solid #facc15' },
low: { backgroundColor: '#dcfce7', color: '#15803d', border: '1px solid #4ade80' },
};
return map[item.priority] || {};
},
};
readonly listConfig: IListConfigItem = {
type: 'checkbox',
configTemplateCheckbox: signal({ fieldKey: 'id', getValue: (item: any) => item.label }),
httpRequestData: signal({ objectInstance: this.tagService, functionName: 'getAll', argumentsValue: [] }) as any,
};
}<!-- your.component.html -->
<libs_ui-components-tags
[config]="config"
[listConfig]="listConfig"
placeholder="Chọn mức độ ưu tiên..."
/>6. Điều khiển từ bên ngoài qua FunctionControl
// your.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsTagsComponent, ITagsConfig, ITagsFunctionControlEvent } from '@libs-ui/components-tags';
@Component({
standalone: true,
imports: [LibsUiComponentsTagsComponent],
templateUrl: './your.component.html',
})
export class YourComponent {
readonly config: ITagsConfig = {};
private tagsControl: ITagsFunctionControlEvent | undefined;
protected handlerFunctionsControl(event: ITagsFunctionControlEvent): void {
event.stopPropagation();
this.tagsControl = event;
}
protected async handlerClearAll(): Promise<void> {
await this.tagsControl?.clearItemSelected();
}
protected async handlerFocusInput(): Promise<void> {
await this.tagsControl?.focusInput();
}
protected async handlerCheckValid(): Promise<void> {
const isValid = await this.tagsControl?.checkIsValid();
// xử lý kết quả
}
}<!-- your.component.html -->
<libs_ui-components-tags
[config]="config"
(outFunctionsControl)="handlerFunctionsControl($event)"
/>
<button (click)="handlerClearAll()">Xóa tất cả</button>
<button (click)="handlerFocusInput()">Focus Input</button>
<button (click)="handlerCheckValid()">Kiểm tra hợp lệ</button>@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [config] | ITagsConfig | undefined | Cấu hình giao diện và logic của tags | [config]="{ uiLikeDropdown: true }" |
| [classAvatarInclude] | string | undefined | Class CSS bổ sung cho avatar | classAvatarInclude="rounded-full" |
| [classIconRemoveTagItem] | string | undefined | Class CSS bổ sung cho icon xóa tag | [classIconRemoveTagItem]="'text-red-500'" |
| [classIncludePopover] | string | undefined | Class CSS bổ sung cho popover container | [classIncludePopover]="'shadow-lg'" |
| [classIncludeTagItem] | string | undefined | Class CSS bổ sung cho mỗi tag item | [classIncludeTagItem]="'font-semibold'" |
| [checkExitByFields] | Array<string> | undefined | Các trường bổ sung để kiểm tra trùng lặp khi nhập | [checkExitByFields]="['email', 'code']" |
| [checkExitIsLabelInclude] | boolean | undefined | Kiểm tra trùng lặp theo includes thay vì === | [checkExitIsLabelInclude]="true" |
| [defaultItemsSelected] | Array<unknown> | undefined | Danh sách item object được chọn mặc định ban đầu | [defaultItemsSelected]="[{ id: '1', label: 'Angular' }]" |
| [defaultKeysSelected] | Array<unknown> | undefined | Danh sách keys được chọn mặc định (cần httpRequestDetailItemByIds để load detail) | [defaultKeysSelected]="['1', '2']" |
| [disable] | boolean | undefined | Vô hiệu hóa toàn bộ component | [disable]="true" |
| [emitEventClickXIcon] | boolean | undefined | Emit outSelectMultiItem khi click icon xóa dù list đang mở | [emitEventClickXIcon]="true" |
| [enterAutoAddTagToList] | boolean | false | Tự động thêm tag nhập mới vào danh sách gợi ý | [enterAutoAddTagToList]="true" |
| [fieldGetImage] | string | undefined | Tên trường lấy URL ảnh avatar | fieldGetImage="avatar" |
| [fieldGetTextAvatar] | string | 'user' | Tên trường lấy text hiển thị cho avatar khi không có ảnh | fieldGetTextAvatar="username" |
| [fieldLabel] | string | 'label' | Tên trường dùng làm nhãn hiển thị cho mỗi tag | fieldLabel="name" |
| [functionCheckPermissionCreateTag] | () => boolean | () => true | Hàm kiểm tra quyền tạo tag mới khi nhấn Enter | [functionCheckPermissionCreateTag]="checkPermission" |
| [getLastTextAfterSpace] | boolean | undefined | Lấy phần text sau khoảng trắng cuối cùng khi tìm kiếm | [getLastTextAfterSpace]="true" |
| [hiddenInputWhenReadonly] | boolean | undefined | Ẩn ô input khi ở chế độ readonly | [hiddenInputWhenReadonly]="true" |
| [httpRequestDetailItemByIds] | IHttpRequestConfig | undefined | Cấu hình API để load chi tiết item theo keys (dùng với defaultKeysSelected) | [httpRequestDetailItemByIds]="detailConfig" |
| [ignoreEmitWhenSameDataSelected] | boolean | undefined | Bỏ qua emit khi data selected không thay đổi | [ignoreEmitWhenSameDataSelected]="true" |
| [imageSize] | TYPE_SIZE_AVATAR_CONFIG | 16 | Kích thước avatar (px) | [imageSize]="24" |
| [isSearchOnline] | boolean | false | Kích hoạt tìm kiếm online qua API (gửi keyword lên server) | [isSearchOnline]="true" |
| [keysDisableItem] | Array<unknown> | undefined | Danh sách keys của các tag không thể bị xóa | [keysDisableItem]="['1', '3']" |
| [keysHiddenItem] | model<Array<unknown>> | undefined | Two-way binding danh sách keys bị ẩn khỏi danh sách | [(keysHiddenItem)]="hiddenKeys" |
| [labelConfig] | ILabel | undefined | Cấu hình label hiển thị phía trên component | [labelConfig]="{ labelLeft: 'Tags' }" |
| [linkImageError] | string | undefined | URL ảnh fallback khi ảnh avatar bị lỗi | linkImageError="assets/default-avatar.png" |
| [listBackgroundListCustom] | string | undefined | Class background tùy chỉnh cho danh sách popover | [listBackgroundListCustom]="'bg-gray-50'" |
| [listButtonsOther] | Array<IButton> | undefined | Các nút bổ sung hiển thị trong popover list | [listButtonsOther]="extraButtons" |
| [listClickExactly] | boolean | undefined | Chỉ cho phép click chính xác vào item (không bubble) | [listClickExactly]="true" |
| [listConfig] | IListConfigItem | undefined | Cấu hình danh sách gợi ý hiển thị trong popover | [listConfig]="listConfig" |
| [listDividerClassInclude] | string | undefined | Class CSS bổ sung cho divider trong danh sách | [listDividerClassInclude]="'my-2'" |
| [listHiddenInputSearchWhenHasSearchConfig] | boolean | undefined | Ẩn input tìm kiếm trong list khi đã có listSearchConfig | [listHiddenInputSearchWhenHasSearchConfig]="true" |
| [listMaxItemShow] | number | undefined | Số lượng item tối đa hiển thị trong popover list | [listMaxItemShow]="10" |
| [listSearchConfig] | IInputSearchConfig | undefined | Cấu hình ô tìm kiếm bên trong popover list | [listSearchConfig]="searchConfig" |
| [listSearchPadding] | boolean | undefined | Thêm padding cho phần tìm kiếm trong list | [listSearchPadding]="true" |
| [maxItemInput] | number | undefined | Giới hạn số lượng tag tối đa có thể chọn | [maxItemInput]="5" |
| [parentBorderWidth] | number | undefined | Độ rộng border của phần tử cha (điều chỉnh vị trí popover) | [parentBorderWidth]="1" |
| [placeholder] | string | undefined | Placeholder text khi chưa có tag nào | placeholder="Tìm kiếm hoặc nhập..." |
| [readonly] | boolean | undefined | Chế độ chỉ đọc, không thể thêm/xóa tag | [readonly]="true" |
| [removeTextSearchWhenBlurInput] | boolean | true | Xóa text tìm kiếm khi blur khỏi input | [removeTextSearchWhenBlurInput]="false" |
| [showBorderActiveWhenFocusInput] | boolean | undefined | Hiển thị border active khi input được focus | [showBorderActiveWhenFocusInput]="true" |
| [showListBellow] | boolean | undefined | Luôn hiển thị danh sách bên dưới (không qua popover overlay) | [showListBellow]="true" |
| [singleSelected] | boolean | undefined | Chế độ chọn đơn (chỉ giữ lại một tag) | [singleSelected]="true" |
| [validMaxItemSelected] | IValidMaxItemSelected | undefined | Cấu hình validation số lượng tối đa | [validMaxItemSelected]="{ value: 3, message: 'Tối đa 3 tag' }" |
| [validPattern] | Array<IValidPattern> | undefined | Cấu hình validation theo pattern regex | [validPattern]="[{ pattern: /^[a-z]+$/, message: 'Chỉ chữ thường' }]" |
| [validRequired] | IMessageTranslate | undefined | Thông báo lỗi khi không có tag nào được chọn | [validRequired]="{ message: 'Trường này bắt buộc' }" |
| [zIndex] | number | 1000 | z-index của popover danh sách | [zIndex]="1500" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outChangStageFlagMouse) | IFlagMouse | Emit trạng thái hover chuột (enter/leave) vào component | handlerMouseState(e: IFlagMouse): void { e.stopPropagation(); } | (outChangStageFlagMouse)="handlerMouseState($event)" |
| (outClickButtonOther) | IButton | Emit khi người dùng click vào nút bổ sung trong danh sách | handlerClickOtherBtn(e: IButton): void { e.stopPropagation(); } | (outClickButtonOther)="handlerClickOtherBtn($event)" |
| (outFunctionsControl) | ITagsFunctionControlEvent | Emit object chứa các method điều khiển component từ bên ngoài | handlerFunctionsControl(e: ITagsFunctionControlEvent): void { e.stopPropagation(); this.ctrl = e; } | (outFunctionsControl)="handlerFunctionsControl($event)" |
| (outLoadingGetDetailSelected) | boolean | Emit trạng thái loading khi đang gọi API load chi tiết item theo keys | handlerDetailLoading(e: boolean): void { e.stopPropagation(); } | (outLoadingGetDetailSelected)="handlerDetailLoading($event)" |
| (outSelectMultiItem) | Array<unknown> | Emit danh sách đầy đủ các object item đã được chọn mỗi khi thay đổi | handlerSelectTags(e: unknown[]): void { e.stopPropagation(); this.selectedItems = e; } | (outSelectMultiItem)="handlerSelectTags($event)" |
| (outShowListEvent) | boolean | Emit khi danh sách popover hiện (true) hoặc ẩn (false) | handlerShowList(e: boolean): void { e.stopPropagation(); } | (outShowListEvent)="handlerShowList($event)" |
| (outValidEvent) | boolean | Emit trạng thái validation (true = hợp lệ, false = có lỗi) | handlerValid(e: boolean): void { e.stopPropagation(); this.isValid = e; } | (outValidEvent)="handlerValid($event)" |
Types & Interfaces
import { ITagsConfig, ITagsFunctionControlEvent } from '@libs-ui/components-tags';ITagsConfig
export interface ITagsConfig {
/** Hàm custom để lấy text nhãn hiển thị cho tag, ưu tiên hơn fieldLabel */
getValue?: (item: unknown) => string;
/**
* Hàm transform giá trị nhãn khi tạo tag mới bằng Enter.
* Nhận item và value hiện tại, có thể mutate item để đổi nhãn.
*/
buildValue?: (item: unknown, value: string) => void;
/** Hàm trả về object NgStyle cho từng tag item (backgroundColor, color, border, ...) */
getStyles?: (item: unknown) => Record<string, string>;
/** Hàm xác định tag có bị disable (không thể xóa) hay không */
getDisableItem?: (item: unknown) => boolean;
/**
* Hàm trả về style đặc biệt theo tên preset.
* 'specific_violet_color' → { color: '#7239EA', backgroundColor: '#F1EBFD' }
* Dùng cho tag do hệ thống gắn tự động, không thể xóa.
*/
getStylesTagSpecific?: (item: unknown) => 'specific_violet_color' | undefined | void;
/** Hàm trả về cấu hình tooltip cho từng tag item */
getTooltip?: (item: unknown) => IPopover;
/** Kích thước avatar (px). Ưu tiên hơn input [imageSize] */
imageSize?: TYPE_SIZE_AVATAR_CONFIG;
/** Tên trường lấy URL ảnh avatar. Ưu tiên hơn input [fieldGetImage] */
fieldGetImage?: string;
/** Tên trường lấy text cho avatar khi không có ảnh. Ưu tiên hơn input [fieldGetTextAvatar] */
fieldGetTextAvatar?: string;
/** URL ảnh fallback khi avatar lỗi */
linkImageError?: string;
/** Lấy từ cuối sau khoảng trắng để tìm kiếm (dùng cho mention @user) */
getLastTextAfterSpace?: boolean;
/** Class CSS bổ sung cho avatar */
classAvatarInclude?: string;
/** Ẩn ô input nhập text (chỉ chọn từ danh sách) */
ignoreInput?: boolean;
/** Class CSS bổ sung cho input bên trong component */
classInputInclude?: string;
/** Ẩn popover danh sách gợi ý (component chỉ hiển thị tags đã chọn) */
ignoreShowList?: boolean;
/**
* Không cho phép tạo tag mới khi nhấn Enter.
* Chỉ cho phép chọn item có sẵn trong danh sách.
*/
ignoreEnterCreateNewItem?: boolean;
/** Ẩn item trong danh sách gợi ý khi item đó đã được chọn */
isHiddenItemWhenSelected?: boolean;
/**
* Chế độ UI giống dropdown classic:
* - Đổi border sang box-shadow
* - Hiển thị icon mũi tên thu gọn bên phải
*/
uiLikeDropdown?: boolean;
/** Ẩn thông báo lỗi validation dưới component */
ignoreShowError?: boolean;
/** Class CSS bổ sung cho thẻ wrapper chính của component */
classInclude?: string;
/** Class CSS áp dụng riêng khi ở chế độ readonly */
classCustomWhenReadOnly?: string;
/** Cấu hình animation cho popover */
animationConfig?: {
time?: number;
distance?: number;
};
/** Hướng hiển thị danh sách popover ('top' | 'bottom' | 'left' | 'right') */
directionList?: TYPE_POPOVER_DIRECTION;
}ITagsFunctionControlEvent
export interface ITagsFunctionControlEvent {
/** Kiểm tra tính hợp lệ của component (required, maxItem, pattern). Trả về true nếu hợp lệ. */
checkIsValid: () => Promise<boolean>;
/** Cập nhật lại text nhãn cho tất cả các tag đang hiển thị */
updateLabel: () => Promise<void>;
/**
* Xóa toàn bộ các tag đã chọn.
* @param ignoreValid - Nếu true, bỏ qua validation sau khi xóa
*/
clearItemSelected: (ignoreValid?: boolean) => Promise<void>;
/** Xóa text tìm kiếm trong input */
clearKeySearch: () => Promise<void>;
/** Focus vào ô input tìm kiếm */
focusInput: () => Promise<void>;
/** Đóng popover danh sách gợi ý */
removeList: () => Promise<void>;
/** Lấy fakeId prefix dùng cho tag mới tạo bằng Enter (chưa có id thực) */
getFakeId: () => Promise<string>;
/** Kích hoạt sự kiện Enter để tạo tag mới từ text hiện tại trong input */
createTag: () => Promise<void>;
/** Reset lỗi validation */
resetError: () => Promise<void>;
/**
* Xóa một tag cụ thể khỏi danh sách đã chọn
* @param event - DOM Event
* @param item - Item cần xóa
*/
removeItemSelected: (event: Event, item: unknown) => Promise<void>;
/**
* Reset về danh sách mặc định ban đầu (defaultItemsSelected / defaultKeysSelected)
* @param item - Nếu truyền vào, set danh sách này thay vì default
*/
resetToDefaultDataSelected: (item?: Array<unknown>) => Promise<void>;
/** Cập nhật lại trạng thái disable cho các tag đã chọn dựa trên config.getDisableItem */
updateItemSelectedDisable: () => Promise<void>;
/** Refresh lại dữ liệu danh sách gợi ý */
refreshListData: () => Promise<void>;
/**
* Set lỗi thủ công từ bên ngoài
* @param message - Thông báo lỗi cần hiển thị
*/
setError?: (message: string) => Promise<void>;
}Lưu ý quan trọng
⚠️ Config object bắt buộc: Luôn truyền [config] dù là object rỗng {}. Nếu không có config, component sẽ return sớm trong ngOnInit và không emit outFunctionsControl.
⚠️ httpRequestDetailItemByIds yêu cầu cấu hình: Khi dùng [defaultKeysSelected] kết hợp với [httpRequestDetailItemByIds], bắt buộc phải cấu hình guideAutoUpdateArgumentsValue.detailById. Thiếu cấu hình này sẽ throw Error runtime.
⚠️ fieldKey mặc định là id: Component lấy fieldKey từ listConfig (thông qua getFieldKeyByType). Nếu listConfig dùng trường khác làm key (ví dụ code), phải cấu hình configTemplateCheckbox.fieldKey hoặc configTemplateText.fieldKey cho đúng.
⚠️ singleSelected và itemsSelected: Khi [singleSelected]="true", mỗi lần chọn item mới sẽ xóa item cũ trước. Kết hợp với config.uiLikeDropdown và config.ignoreInput để có giao diện dropdown hoàn chỉnh.
⚠️ Tag mới tạo bằng Enter (fakeId): Tags tạo bằng Enter sẽ có id dạng {fakeId}{text}{uuid}. Khi emit qua outSelectMultiItem, trường key của tag này được set về chuỗi rỗng ''. Consumer cần xử lý trường hợp id === '' để phân biệt tag mới chưa lưu.
⚠️ Cleanup timeout: Component tự dọn dẹp các setTimeout trong ngOnDestroy. Không cần xử lý thêm từ phía consumer.
⚠️ outFunctionsControl không phải Observable: outFunctionsControl là OutputEmitterRef (Signal API). Không được .pipe() hoặc takeUntilDestroyed trên output này.
Demo
npx nx serve core-uiTruy cập: http://localhost:4500/tags
