@libs-ui/components-pagination
v0.2.357-11
Published
> Component phân trang Angular standalone, hỗ trợ two-way binding, nhảy trang nhanh và debounce input.
Downloads
2,563
Readme
@libs-ui/components-pagination
Component phân trang Angular standalone, hỗ trợ two-way binding, nhảy trang nhanh và debounce input.
Giới thiệu
LibsUiComponentsPaginationComponent là standalone Angular component giúp chia nhỏ dữ liệu thành các trang và cung cấp trải nghiệm điều hướng mượt mà. Component hỗ trợ two-way binding qua model(), tự động tính toán cửa sổ trang hiển thị theo trang hiện tại, và tích hợp ô nhập nhảy trang với debounce 1500ms. Phù hợp dùng với mọi bảng dữ liệu hoặc danh sách lớn cần phân trang phía client hoặc server-side.
Tính năng
- ✅ Two-way binding qua
[(currentPage)]— đồng bộ trang hiện tại hai chiều - ✅ Hiển thị tổng trang — tùy chọn
[showTotalPage]hiển thị nhãn "X trang" - ✅ Nhảy trang nhanh — ô input "Go to page" với debounce 1500ms, nhấn Enter nhảy ngay
- ✅ Bố cục linh hoạt — vùng tổng trang & nhảy trang đặt được ở top / bottom / left / right
- ✅ Sliding window — danh sách số trang hiển thị trượt theo trang hiện tại, tự điều chỉnh số lượng
- ✅ Boundary protection — tự kéo
currentPagevề trang cuối khi dữ liệu thay đổi làm số trang giảm - ✅ Disable state — vô hiệu hóa toàn bộ điều hướng qua
[disable] - ✅ Custom class — tùy chỉnh class cho container, từng nút trang và vùng thông tin phụ
- ✅ Angular Signals + OnPush — hiệu năng cao, không re-render thừa
- ✅ firstPageStartNumber — hỗ trợ quy ước trang bắt đầu từ 0 hoặc 1
Khi nào sử dụng
- Khi có danh sách hoặc bảng dữ liệu lớn cần chia thành nhiều trang để tối ưu hiệu suất
- Khi cần cung cấp điều hướng rõ ràng (Previous / Next / số trang) cho người dùng
- Khi người dùng cần nhảy nhanh đến một trang cụ thể bằng cách nhập số trang
- Khi cần tích hợp phân trang server-side (gọi API mới mỗi khi
outPageSelectemit)
Cài đặt
npm install @libs-ui/components-paginationImport
import { LibsUiComponentsPaginationComponent } from '@libs-ui/components-pagination';
@Component({
standalone: true,
imports: [LibsUiComponentsPaginationComponent],
// ...
})
export class YourComponent {}Types & Interfaces
import {
IPaginationConfig,
IPaginationFunctionsControl,
} from '@libs-ui/components-pagination';Ví dụ sử dụng
1. Phân trang cơ bản
// your.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsPaginationComponent } from '@libs-ui/components-pagination';
@Component({
selector: 'app-your',
standalone: true,
imports: [LibsUiComponentsPaginationComponent],
templateUrl: './your.component.html',
})
export class YourComponent {
protected currentPage = signal<number>(1);
protected handlerPageSelect(page: number): void {
event?.stopPropagation?.();
console.log('Chuyển sang trang:', page);
// Gọi API để load dữ liệu trang mới
}
}<!-- your.component.html -->
<libs_ui-components-pagination
[totalItems]="100"
[perPage]="10"
[(currentPage)]="currentPage"
(outPageSelect)="handlerPageSelect($event)" />2. Hiển thị tổng trang và nhảy trang nhanh (bên phải)
// your.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsPaginationComponent } from '@libs-ui/components-pagination';
@Component({
selector: 'app-your',
standalone: true,
imports: [LibsUiComponentsPaginationComponent],
templateUrl: './your.component.html',
})
export class YourComponent {
protected currentPage = signal<number>(1);
protected totalItems = signal<number>(500);
protected handlerPageSelect(page: number): void {
event?.stopPropagation?.();
// Load dữ liệu trang mới từ API
}
}<!-- your.component.html -->
<libs_ui-components-pagination
[totalItems]="totalItems()"
[perPage]="20"
[(currentPage)]="currentPage"
[showTotalPage]="true"
[showInputGotoPage]="true"
[modeDisplayTotalPageAndGotoPage]="'right'"
(outPageSelect)="handlerPageSelect($event)" />3. Trang bắt đầu từ 0 (zero-based) với vùng thông tin phụ ở trên
// your.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsPaginationComponent } from '@libs-ui/components-pagination';
@Component({
selector: 'app-your',
standalone: true,
imports: [LibsUiComponentsPaginationComponent],
templateUrl: './your.component.html',
})
export class YourComponent {
protected currentPage = signal<number>(0);
protected handlerPageSelect(page: number): void {
event?.stopPropagation?.();
// page = 0, 1, 2, ... (zero-based)
}
}<!-- your.component.html -->
<libs_ui-components-pagination
[totalItems]="200"
[perPage]="10"
[(currentPage)]="currentPage"
[firstPageStartNumber]="0"
[showTotalPage]="true"
[showInputGotoPage]="true"
[modeDisplayTotalPageAndGotoPage]="'top'"
(outPageSelect)="handlerPageSelect($event)" />4. Disabled state
<libs_ui-components-pagination
[totalItems]="100"
[perPage]="10"
[(currentPage)]="currentPage"
[disable]="isLoading()" />5. Tùy chỉnh class container và nút trang
<libs_ui-components-pagination
[totalItems]="100"
[perPage]="10"
[(currentPage)]="currentPage"
[numberPageDisplay]="7"
classIncludeContainer="mt-4"
classIncludeItem="w-[36px] h-[36px]"
[showTotalPage]="true"
classDisplayTotalPageAndGotoPage="gap-2"
(outPageSelect)="handlerPageSelect($event)" />6. Dùng FunctionsControl qua ViewChild
// parent.component.ts
import { Component, viewChild } from '@angular/core';
import {
LibsUiComponentsPaginationComponent,
IPaginationFunctionsControl,
} from '@libs-ui/components-pagination';
@Component({
selector: 'app-parent',
standalone: true,
imports: [LibsUiComponentsPaginationComponent],
template: `
<libs_ui-components-pagination
#paginationRef
[totalItems]="totalItems()"
[perPage]="20"
[(currentPage)]="currentPage" />
<button (click)="handlerResetPage()">Reset trang</button>
`,
})
export class ParentComponent {
private readonly paginationRef = viewChild<LibsUiComponentsPaginationComponent>('paginationRef');
protected currentPage = signal<number>(1);
protected totalItems = signal<number>(200);
protected handlerResetPage(): void {
event?.stopPropagation?.();
this.paginationRef()?.FunctionsControl.changePage(1);
}
}@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [totalItems] | number | Bắt buộc | Tổng số bản ghi cần phân trang | [totalItems]="500" |
| [perPage] | number | Bắt buộc | Số bản ghi hiển thị trên mỗi trang | [perPage]="20" |
| [(currentPage)] | number | Bắt buộc | Trang hiện tại — two-way binding qua model() | [(currentPage)]="currentPage" |
| [numberPageDisplay] | number | 5 | Số lượng nút số trang tối đa hiển thị trên thanh điều hướng | [numberPageDisplay]="7" |
| [showTotalPage] | boolean | undefined | Hiển thị nhãn tổng số trang (vd: "10 trang") | [showTotalPage]="true" |
| [showInputGotoPage] | boolean | undefined | Hiển thị ô input để nhảy nhanh đến trang bất kỳ | [showInputGotoPage]="true" |
| [modeDisplayTotalPageAndGotoPage] | 'top' \| 'bottom' \| 'left' \| 'right' | 'bottom' | Vị trí hiển thị vùng tổng trang và nhảy trang so với thanh số trang | [modeDisplayTotalPageAndGotoPage]="'right'" |
| [classDisplayTotalPageAndGotoPage] | string | undefined | Class CSS tùy chỉnh cho vùng chứa thông tin tổng trang và nhảy trang | classDisplayTotalPageAndGotoPage="gap-4" |
| [classIncludeContainer] | string | undefined | Class CSS tùy chỉnh cho wrapper ngoài cùng của component | classIncludeContainer="mt-4 justify-end" |
| [classIncludeItem] | string | undefined | Class CSS tùy chỉnh cho từng nút số trang | classIncludeItem="w-[40px] h-[40px]" |
| [disable] | boolean | undefined | Vô hiệu hóa toàn bộ điều hướng (click, nhập số trang) | [disable]="isLoading()" |
| [firstPageStartNumber] | 0 \| 1 | 1 | Quy ước số trang bắt đầu: 1 = one-based (mặc định), 0 = zero-based | [firstPageStartNumber]="0" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outPageSelect) | number | Emit số trang mới mỗi khi người dùng chuyển trang (click số trang, Previous, Next, hoặc Go to page) | handlerPageSelect(page: number): void { event?.stopPropagation?.(); /* gọi API */ } | (outPageSelect)="handlerPageSelect($event)" |
FunctionsControl (ViewChild API)
Truy cập qua viewChild để điều khiển component từ bên ngoài:
private readonly paginationRef = viewChild<LibsUiComponentsPaginationComponent>('paginationRef');
// Gọi changePage với số trang cụ thể
this.paginationRef()?.FunctionsControl.changePage(3);
// Gọi changePage không có tham số → re-render trang hiện tại
this.paginationRef()?.FunctionsControl.changePage();| Method | Signature | Mô tả |
|---|---|---|
| changePage | (page?: number) => Promise<void> | Chuyển đến trang page. Nếu không truyền page, re-render trang hiện tại |
Types & Interfaces
import {
IPaginationConfig,
IPaginationFunctionsControl,
} from '@libs-ui/components-pagination';
// IPaginationConfig — dùng để định nghĩa config tập trung
type IPaginationConfig = {
position?: 'top' | 'bottom';
classIncludeContainer?: string;
classIncludeItem?: string;
disable?: boolean;
numberPageDisplay?: number;
showTotalPage?: boolean;
firstPageStartNumber?: 0 | 1;
showInputGotoPage?: boolean;
modeDisplayTotalPageAndGotoPage?: 'top' | 'bottom' | 'left' | 'right';
classDisplayTotalPageAndGotoPage?: string;
perPage?: number;
totalItems?: number;
currentPage?: number;
};
// IPaginationFunctionsControl — interface của FunctionsControl getter
interface IPaginationFunctionsControl {
changePage: (page?: number) => Promise<void>;
}Logic ẩn quan trọng
Debounced Navigation (1500ms)
Khi người dùng nhập số vào ô "Go to page", component đợi 1500ms sau lần gõ cuối mới thực hiện chuyển trang, tránh spam API. Nhấn Enter sẽ bỏ qua thời gian chờ và chuyển trang ngay lập tức.
Sliding Window (cửa sổ trang trượt)
Danh sách các nút số trang hiển thị được tính động, luôn bao quanh currentPage trong phạm vi numberPageDisplay. Khi currentPage thay đổi, cửa sổ trang trượt theo để trang hiện tại luôn nằm ở giữa (hoặc gần giữa).
Boundary Protection (giới hạn biên)
Nếu totalItems thay đổi làm giảm tổng số trang xuống thấp hơn currentPage hiện tại, component tự động kéo currentPage về trang cuối hợp lệ và emit outPageSelect.
firstPageStartNumber
firstPageStartNumber="1"(mặc định):currentPagenhận giá trị1, 2, 3, ..., emit1, 2, 3, ...firstPageStartNumber="0":currentPagenhận giá trị0, 1, 2, ..., emit0, 1, 2, ...
Dùng firstPageStartNumber="0" khi API backend dùng quy ước zero-based pagination.
Lưu ý quan trọng
⚠️ Two-way binding bắt buộc: Luôn dùng [(currentPage)] thay vì [currentPage] một chiều. Nếu chỉ dùng một chiều, component không thể cập nhật lại số trang hiển thị khi người dùng điều hướng.
⚠️ Required inputs: totalItems, perPage và currentPage là bắt buộc. Thiếu bất kỳ input nào sẽ khiến component không thể tính toán số trang và không render được gì.
⚠️ Debounce 1500ms: Ô "Go to page" có delay 1500ms. Nếu UX yêu cầu chuyển trang ngay lập tức khi nhập, hướng dẫn người dùng nhấn Enter sau khi nhập.
⚠️ Disable khi loading: Nên bind [disable]="isLoading()" để ngăn người dùng chuyển trang trong lúc đang gọi API, tránh race condition.
