@libs-ui/interfaces-types
v0.2.357-9
Published
> Bộ sưu tập tập trung các Interface và Type TypeScript dùng chung toàn hệ thống libs-ui.
Downloads
4,395
Readme
@libs-ui/interfaces-types
Bộ sưu tập tập trung các Interface và Type TypeScript dùng chung toàn hệ thống libs-ui.
Giới thiệu
@libs-ui/interfaces-types là thư viện thuần TypeScript cung cấp các interface và type được tái sử dụng xuyên suốt hệ sinh thái @libs-ui. Lib bao gồm các định nghĩa cho HTTP response/upload, phân trang, file, sự kiện trình duyệt, ngày tháng (dayjs), cũng như các utility type nâng cao phục vụ Angular Signals như ExtractInputs, PathOf, GetValueAtPath, và SignalOf.
Tính năng
- ✅ Định nghĩa chuẩn cho HTTP Response và phân trang (
IHttpResponse,IPaging) - ✅ Utility type trích xuất Input Signal từ Angular component (
ExtractInputs) - ✅ Type-safe path navigation cho object/signal lồng nhau (
PathOf,GetValueAtPath) - ✅ Chuyển đổi kiểu dữ liệu sang dạng Signal và ngược lại (
SignalOf,NonSignalOf) - ✅ Định nghĩa cho sự kiện trình duyệt mở rộng (
IEvent,IEventTarget) - ✅ Interface quản lý file upload và tiến trình (
IFile,IHttpProcessUpload) - ✅ Type ngày tháng với dayjs tích hợp (
GetDayjsConfig,GetDayjsReturn) - ✅ Interface validator và định dạng chuỗi (
IIsValidRequired,ITextFormatOptions) - ✅ Type tỉ lệ khung hình và toạ độ DOM (
TYPE_ASPECT_RATIO,IBoundingClientRect)
Khi nào sử dụng
- Khi cần định nghĩa kiểu cho response API trả về từ backend
- Khi truyền data vào Modal V2 qua
of<ExtractInputs<...>>()để đảm bảo type-safe - Khi xây dựng hàm generic truy cập giá trị theo đường dẫn chuỗi trên object/signal lồng nhau
- Khi cần chuyển đổi một interface sang dạng Signal-wrapped để dùng với Angular Signals
- Khi xây dựng form validator và cần type chuẩn cho message lỗi
- Khi xử lý file upload và cần theo dõi tiến trình phần trăm
Cài đặt
npm install @libs-ui/interfaces-typesImport
// HTTP & Paging
import { IHttpResponse, IHttpResponseError, IPaging } from '@libs-ui/interfaces-types';
// File & Upload
import { IFile, TYPE_FILE, IHttpProcessUpload } from '@libs-ui/interfaces-types';
// Angular Signal Utilities
import { ExtractInputs } from '@libs-ui/interfaces-types';
import { PathOf, GetValueAtPath, GetReturnType, SignalOf, NonSignalOf } from '@libs-ui/interfaces-types';
// Event browser
import { IEvent, IEventTarget, IEventCustom } from '@libs-ui/interfaces-types';
// Date (dayjs)
import { GetDayjsConfig, GetDayjsReturn, NonNullableDate } from '@libs-ui/interfaces-types';
// DOM
import { IBoundingClientRect } from '@libs-ui/interfaces-types';
import { TYPE_ASPECT_RATIO, IAspectRatio } from '@libs-ui/interfaces-types';
// Validator
import { IIsValidRequired, IIsValidPattern, IIsValidLength, IMessageTranslate } from '@libs-ui/interfaces-types';
// String
import { IString, IStringCustom, ITextFormatOptions } from '@libs-ui/interfaces-types';
// Others
import { TYPE_OBJECT } from '@libs-ui/interfaces-types';
import { TYPE_LANGUAGE_SUPPORT } from '@libs-ui/interfaces-types';
import { TYPE_FUNCTION } from '@libs-ui/interfaces-types';
import { TYPE_IFRAME_MESSAGE } from '@libs-ui/interfaces-types';
import { TYPE_TEMPLATE_REF } from '@libs-ui/interfaces-types';Ví dụ sử dụng
1. IHttpResponse — Định nghĩa kiểu trả về từ API
import { IHttpResponse, IPaging } from '@libs-ui/interfaces-types';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { Observable } from 'rxjs';
interface IUser {
id: string;
name: string;
email: string;
}
// Service gọi API danh sách user có phân trang
export class UserApiService {
private readonly http = inject(HttpClient);
getList(params: { page: number; per_page: number }): Observable<IHttpResponse<IUser[]>> {
return this.http.get<IHttpResponse<IUser[]>>('/api/users', { params });
}
}
// Component nhận response
// component.ts
protected handlerLoadData(): void {
this.userApiService.getList({ page: 1, per_page: 20 })
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((res: IHttpResponse<IUser[]>) => {
if (res.code !== 200) return;
this.users.set(res.data ?? []);
this.paging.set(res.paging ?? {});
});
}2. ExtractInputs — Truyền data type-safe vào Modal V2
ExtractInputs<T> trích xuất tất cả InputSignal từ một component thành một plain object type, dùng khi gọi of<ExtractInputs<...>>() để truyền data vào getDataComponentOutlet.
import { ExtractInputs } from '@libs-ui/interfaces-types';
import { InputSignal, input } from '@angular/core';
import { of } from 'rxjs';
import { from } from 'rxjs';
import { LibsUiComponentsModalV2Component } from '@libs-ui/components-modal-v2';
// Định nghĩa interface khớp với component con
interface I_DataInComponentUserDetail {
userId: InputSignal<string>; // required
mode: InputSignal<'view' | 'edit'>; // required
title?: InputSignal<string>; // optional
}
// Component con implements interface
export class UserDetailModalComponent implements I_DataInComponentUserDetail {
readonly userId = input.required<string>();
readonly mode = input.required<'view' | 'edit'>();
readonly title = input<string>();
}
// Component cha truyền data qua getDataComponentOutlet
this.libsUiDynamicComponentService.setInputs(this.modalComponentRef, {
bodyConfig: {
component: () => from(
import('./user-detail-modal/user-detail-modal.component')
.then(m => m.UserDetailModalComponent)
),
getDataComponentOutlet: () => of<ExtractInputs<I_DataInComponentUserDetail>>({
userId: 'user-123', // required — bắt buộc truyền
mode: 'view', // required — bắt buộc truyền
// title: 'Chi tiết', // optional — có thể bỏ qua
}),
},
});3. PathOf & GetValueAtPath — Truy cập an toàn theo đường dẫn chuỗi
PathOf<T> sinh ra union type của tất cả đường dẫn hợp lệ trong object T. GetValueAtPath<T, P> trả về type của giá trị tại đường dẫn P.
import { PathOf, GetValueAtPath } from '@libs-ui/interfaces-types';
import { WritableSignal, signal } from '@angular/core';
interface IOrder {
id: string;
customer: {
name: string;
address: {
city: string;
zip: string;
};
};
items: Array<{
productId: string;
quantity: number;
}>;
}
// PathOf<IOrder> tự động sinh ra:
// 'id' | 'customer' | 'customer.name' | 'customer.address' |
// 'customer.address.city' | 'customer.address.zip' |
// 'items' | 'items[0]' | 'items[0].productId' | 'items[0].quantity' | ...
type OrderPaths = PathOf<IOrder>;
// GetValueAtPath trả về type chính xác tại path
type CityType = GetValueAtPath<IOrder, 'customer.address.city'>; // string
type ItemQty = GetValueAtPath<IOrder, 'items[0].quantity'>; // number
// Dùng trong hàm generic type-safe
function getField<T, P extends PathOf<T>>(
obj: T,
path: P
): GetValueAtPath<T, P> {
// implementation dùng get() từ @libs-ui/utils
return (obj as any)[path as string];
}4. SignalOf — Chuyển đổi interface sang Signal-wrapped
import { SignalOf, NonSignalOf } from '@libs-ui/interfaces-types';
import { signal, WritableSignal } from '@angular/core';
import { convertObjectToSignal, convertSignalToObject } from '@libs-ui/utils';
interface IUserProfile {
name: string;
age: number;
address: {
city: string;
country: string;
};
tags: string[];
}
// SignalOf<IUserProfile> sẽ sinh ra:
// {
// name: WritableSignal<string>;
// age: WritableSignal<number>;
// address: WritableSignal<{ city: WritableSignal<string>; country: WritableSignal<string>; }>;
// tags: WritableSignal<WritableSignal<string>[]>;
// }
type SignaledProfile = SignalOf<IUserProfile>;
// Sử dụng với convertObjectToSignal từ @libs-ui/utils
const profileSignal = convertObjectToSignal<IUserProfile>({
name: 'Nguyễn Văn A',
age: 30,
address: { city: 'Hà Nội', country: 'Việt Nam' },
tags: ['admin', 'dev'],
});
// Chuyển ngược lại về plain object để gửi API
const payload = convertSignalToObject(profileSignal);5. IFile & IHttpProcessUpload — Quản lý file upload
import { IFile, TYPE_FILE, IHttpProcessUpload } from '@libs-ui/interfaces-types';
import { signal } from '@angular/core';
// component.ts
protected uploadingFiles = signal<IFile[]>([]);
protected uploadProgress = signal<IHttpProcessUpload>({ loaded: 0, total: 0, percent: 0 });
protected handlerFileSelect(event: Event): void {
event.stopPropagation();
const input = event.target as HTMLInputElement;
if (!input.files?.length) return;
const newFiles: IFile[] = Array.from(input.files).map(file => ({
name: file.name,
file: file,
size: `${(file.size / 1024).toFixed(1)} KB`,
type: this.detectFileType(file.type),
isUploading: true,
percentUploading: 0,
}));
this.uploadingFiles.set(newFiles);
}
private detectFileType(mimeType: string): TYPE_FILE {
if (mimeType.startsWith('image/')) return 'image';
if (mimeType.startsWith('video/')) return 'video';
if (mimeType.startsWith('audio/')) return 'audio';
return 'document';
}6. IEvent & IEventTarget — Xử lý sự kiện DOM mở rộng
import { IEvent } from '@libs-ui/interfaces-types';
// component.ts
protected handlerInputChange(event: IEvent): void {
event.stopPropagation();
const value = event.target.value as string;
this.searchTerm.set(value.trim());
}
protected handlerFileDrop(event: IEvent): void {
event.stopPropagation();
const files = event.dataTransfer?.files;
if (!files?.length) return;
this.processFiles(files);
}
protected handlerKeyDown(event: IEvent): void {
event.stopPropagation();
if (event.keyCode === 13) {
this.handlerSubmit();
return;
}
if (event.keyCode === 27) {
this.handlerClose();
}
}7. GetDayjsConfig & GetDayjsReturn — Type cho hàm getDayjs
import { GetDayjsConfig, GetDayjsReturn, NonNullableDate } from '@libs-ui/interfaces-types';
import { getDayjs } from '@libs-ui/utils';
// Định nghĩa hàm format ngày với type rõ ràng
function formatDateSafe(config: GetDayjsConfig): string {
const result = getDayjs(config);
if (!result) return '';
return result.format('DD/MM/YYYY HH:mm');
}
// Ví dụ sử dụng
const formatted1 = formatDateSafe({ date: '2024-06-15', formatOfDate: 'YYYY-MM-DD' });
// => '15/06/2024 00:00'
const formatted2 = formatDateSafe({
date: 1718409600000,
utc: false,
returnDayjsIfConfigDateNotExist: true,
});8. IPaging — Phân trang
import { IPaging, IHttpResponse } from '@libs-ui/interfaces-types';
import { signal, computed } from '@angular/core';
// component.ts
protected paging = signal<IPaging>({});
protected totalPages = computed(() => this.paging().total_pages ?? 0);
protected currentPage = computed(() => this.paging().page ?? 1);
protected hasNextPage = computed(() => !!this.paging().next);
protected handlerPageChange(page: number): void {
this.loadData({ page, per_page: 20 });
}
private loadData(params: { page: number; per_page: number }): void {
this.apiService.getList(params)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((res: IHttpResponse<unknown[], IPaging>) => {
if (res.code !== 200) return;
this.paging.set(res.paging ?? {});
});
}9. Validator interfaces — Cấu hình rule validate
import {
IIsValidRequired,
IIsValidPattern,
IIsValidLength,
IMessageTranslate,
} from '@libs-ui/interfaces-types';
// Cấu hình validator cho input email
const emailValidators = {
required: {
isRequired: true,
message: 'i18n_email_required',
} satisfies IIsValidRequired,
pattern: {
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: 'i18n_email_invalid',
valuePatternShowError: true,
} satisfies IIsValidPattern,
maxLength: {
length: 100,
message: 'i18n_email_too_long',
interpolateParams: { max: 100 },
} satisfies IIsValidLength,
};10. TYPE_LANGUAGE_SUPPORT — Kiểu ngôn ngữ được hỗ trợ
import { TYPE_LANGUAGE_SUPPORT } from '@libs-ui/interfaces-types';
import { signal } from '@angular/core';
// component.ts
protected currentLang = signal<TYPE_LANGUAGE_SUPPORT>('vi');
protected handlerSwitchLang(lang: TYPE_LANGUAGE_SUPPORT): void {
this.currentLang.set(lang);
this.translateService.use(lang);
}11. IAspectRatio & TYPE_ASPECT_RATIO — Tỉ lệ khung hình
import { IAspectRatio, TYPE_ASPECT_RATIO } from '@libs-ui/interfaces-types';
import { signal } from '@angular/core';
const aspectRatioOptions: IAspectRatio[] = [
{ key: 'Tự do', value: 0 },
{ key: '1:1', value: 1 },
{ key: '16:9', value: 16 / 9 },
{ key: '4:3', value: 4 / 3 },
];
// component.ts
protected selectedRatio = signal<TYPE_ASPECT_RATIO>('16:9');
protected handlerSelectRatio(ratio: TYPE_ASPECT_RATIO): void {
this.selectedRatio.set(ratio);
}12. IBoundingClientRect — Toạ độ và kích thước DOM
import { IBoundingClientRect } from '@libs-ui/interfaces-types';
import { ElementRef, inject } from '@angular/core';
import { signal } from '@angular/core';
// component.ts (dùng trong directive hoặc component tính toán vị trí)
private readonly elementRef = inject(ElementRef);
protected boundingRect = signal<IBoundingClientRect>({
top: 0, left: 0, width: 0, height: 0,
});
ngAfterViewInit(): void {
const rect = this.elementRef.nativeElement.getBoundingClientRect();
this.boundingRect.set({
top: rect.top,
left: rect.left,
width: rect.width,
height: rect.height,
right: rect.right,
bottom: rect.bottom,
});
}13. TYPE_IFRAME_MESSAGE — Giao tiếp giữa các micro-frontend
import { TYPE_IFRAME_MESSAGE } from '@libs-ui/interfaces-types';
type MessageType = 'AUTH_TOKEN' | 'PAGE_READY' | 'CLOSE_MODAL';
type MessagePayload = { token: string } | { route: string } | null;
type AppMessage = TYPE_IFRAME_MESSAGE<MessageType, MessagePayload>;
// Gửi message
const msg: AppMessage = { type: 'AUTH_TOKEN', response: { token: 'demo-token' } };
window.parent.postMessage(msg, '*');
// Nhận message
window.addEventListener('message', (event) => {
const data = event.data as AppMessage;
if (data.type === 'AUTH_TOKEN') {
// xử lý token
}
});Types & Interfaces
HTTP
| Export | Loại | Mô tả |
|---|---|---|
| IHttpResponse<T, P> | type | Kiểu chuẩn cho HTTP response từ backend. T = data type, P = paging type |
| IHttpResponseError | interface | Lỗi HTTP mở rộng từ Error, có thêm code |
| IPaging | interface | Phân trang hỗ trợ cả cursor-based và offset-based |
| IHttpProcessUpload | interface | Tiến trình upload: loaded, total, percent |
// IHttpResponse chi tiết
type IHttpResponse<T = any, P = IPaging> = {
code?: number; // HTTP status code
feCloneCode?: any; // Code override khi server trả 200 nhưng logic khác
message?: string; // Thông báo từ server
data?: T; // Dữ liệu chính
paging?: P; // Thông tin phân trang
[key: string]: unknown; // Các field mở rộng
};
// IPaging chi tiết
interface IPaging {
page?: number; // Trang hiện tại (offset-based)
per_page?: number; // Số item mỗi trang
total_items?: number; // Tổng số item
total_pages?: number; // Tổng số trang
before?: string; // Cursor trước (cursor-based)
after?: string; // Cursor sau (cursor-based)
previous?: string; // URL trang trước
next?: string; // URL trang sau
fake?: boolean; // Dữ liệu giả (dùng trong loading state)
}File
| Export | Loại | Mô tả |
|---|---|---|
| IFile | interface | Đối tượng file đầy đủ: id, name, url, type, upload progress |
| TYPE_FILE | type | 'document' \| 'image' \| 'video' \| 'audio' |
interface IFile {
id?: string;
name?: string;
file?: File; // File object gốc từ input[type=file]
size?: string; // Chuỗi kích thước (vd: '2.5 MB')
isUploading?: boolean;
percentUploading?: number; // 0-100
isUpdate?: boolean; // Đang cập nhật file cũ
url?: string; // URL sau khi upload xong
origin_url?: string; // URL gốc
mimetype?: string; // MIME type (vd: 'image/png')
type?: TYPE_FILE;
error?: string;
isAvatar?: boolean;
}Angular Signal Utilities
| Export | Loại | Mô tả |
|---|---|---|
| ExtractInputs<T, R> | type | Trích xuất InputSignal / ModelSignal từ component thành plain object type. R=true (mặc định) giữ nguyên required/optional |
| PathOf<T, Depth> | type | Sinh union type tất cả đường dẫn hợp lệ trong object T, hỗ trợ signal lồng nhau, depth mặc định 6 |
| GetValueAtPath<T, P, R> | type | Resolve type giá trị tại path P. R=false (mặc định) unwrap signal, R=true giữ nguyên signal |
| GetReturnType<O, P, KS, D> | type | Kiểu trả về của get() dựa trên defaultValue D — loại null \| undefined nếu D có giá trị |
| SignalOf<T, P, F> | type | Chuyển toàn bộ object sang dạng Signal-wrapped. P=true (mặc định) wrap cả primitive |
| NonSignalOf<T> | type | Unwrap toàn bộ Signal về plain type |
// Ví dụ ExtractInputs
interface I_DataInComponentBadge {
label: InputSignal<string>; // required
variant: InputSignal<'primary' | 'secondary'>; // required
count?: InputSignal<number>; // optional (có ? vì InputSignal<number | undefined>)
}
type BadgeData = ExtractInputs<I_DataInComponentBadge>;
// => { label: string; variant: 'primary' | 'secondary'; count?: number }
// R=false: optional field có thể bỏ qua
type BadgeDataPartial = ExtractInputs<I_DataInComponentBadge, false>;
// => { label?: string; variant?: string; count?: number }Event Browser
| Export | Loại | Mô tả |
|---|---|---|
| IEvent | interface | Kết hợp Event, MouseEvent, KeyboardEvent, ErrorEvent và custom |
| IEventTarget | interface | EventTarget mở rộng với value, files, style, innerHTML,... |
| IEventCustom | interface | Event tuỳ chỉnh với data, fakeEvent, inputType |
Date
| Export | Loại | Mô tả |
|---|---|---|
| GetDayjsConfig | type | Config truyền vào getDayjs(): date, utc, formatOfDate, returnDayjsIfConfigDateNotExist, localeZone |
| GetDayjsReturn<C> | type | Kiểu trả về điều kiện của getDayjs(): dayjs.Dayjs hoặc undefined tùy config |
| NonNullableDate | type | dayjs.ConfigType loại bỏ null \| undefined |
DOM & Layout
| Export | Loại | Mô tả |
|---|---|---|
| IBoundingClientRect | interface | top, left, width, height, right?, bottom? |
| IAspectRatio | interface | key: string, value: number |
| TYPE_ASPECT_RATIO | type | 'free' \| '1:1' \| '2:3' \| '3:2' \| '3:4' \| '4:3' \| '9:16' \| '16:9' |
Validator
| Export | Loại | Mô tả |
|---|---|---|
| IIsValidRequired | interface | Validator required: isRequired, message, interpolateParams |
| IIsValidPattern | interface | Validator regex: pattern, valuePatternShowError, message |
| IIsValidLength | interface | Validator độ dài: length, message, interpolateParams |
| IMessageTranslate | interface | Base interface cho message lỗi: message?, interpolateParams? |
String
| Export | Loại | Mô tả |
|---|---|---|
| IString | type | String & string & IStringCustom — chuỗi mở rộng |
| IStringCustom | interface | Thêm replaceAt, replaceAll, occurrencesByCharacter, indexesOfCharacter, indexesByString |
| ITextFormatOptions | interface | Options format text: uppercaseOtherCharacter, lowercaseOtherCharacter, trim, removeMultipleSpace, removeEmoji, removeUnicode |
Khác
| Export | Loại | Mô tả |
|---|---|---|
| TYPE_OBJECT | type | Record<any, any> — object không định kiểu. Chỉ dùng khi có lý do, phải kèm comment // CONVENTION-EXCEPT: TYPE_OBJECT — [lý do] |
| TYPE_LANGUAGE_SUPPORT | type | 'vi' \| 'en' — ngôn ngữ được hỗ trợ hiện tại |
| TYPE_FUNCTION<T> | type | (data: { value: any; item?: any; otherData?: any }) => Observable<T> |
| TYPE_IFRAME_MESSAGE<T, U> | type | { type: T; response: U } — message giao tiếp giữa micro-frontend qua iframe |
| TYPE_TEMPLATE_REF | type | any — placeholder type cho TemplateRef chưa xác định |
Lưu ý quan trọng
⚠️ ExtractInputs yêu cầu Generic Type tường minh: Khi truyền data qua of() vào getDataComponentOutlet, BẮT BUỘC khai báo of<ExtractInputs<I_DataInComponent...>>({...}). Dùng of({...}) không có generic sẽ mất type-safety và TypeScript không báo lỗi thiếu field.
⚠️ PathOf có Depth giới hạn mặc định là 6: Với object lồng nhau sâu hơn 6 cấp, các path vượt quá sẽ trả về never. Có thể tăng Depth khi cần: PathOf<T, 8>.
⚠️ SignalOf không wrap các kiểu đặc biệt thêm lần nữa: Dayjs, Date, File, Blob, HttpParams, FormData, Event, Node, Map, Set, RegExp và các Promise/Function sẽ chỉ được wrap 1 cấp signal — không đệ quy vào bên trong.
⚠️ TYPE_OBJECT cần comment lý do: Theo coding convention, mọi nơi sử dụng TYPE_OBJECT phải kèm comment // CONVENTION-EXCEPT: TYPE_OBJECT — [lý do cụ thể]. Không dùng tuỳ tiện.
⚠️ IHttpResponse.feCloneCode là trường đặc biệt: Trường này chỉ được gán từ phía frontend khi muốn override logic sau khi server trả về HTTP 200 nhưng business code lại là giá trị khác. Không phải field từ backend.
⚠️ GetValueAtPath với R=false/true khác nhau: Tham số R=false (mặc định) sẽ unwrap toàn bộ WritableSignal về giá trị thuần. R=true giữ nguyên cấu trúc signal. Lựa chọn R phải nhất quán với mục đích sử dụng.
