- UI组件
- 前端
【免费下载链接】ngx-bootstrap
Fast and reliable Bootstrap widgets in Angular (supports Ivy engine)
本文以 ngx-bootstrap 官方用例规格 pagination.examples.basic.use-case.md 为骨架,完整拆解 Pagination 组件"基础示例(Basic)"的用户场景、默认行为与底层源码实现。读者读完可以掌握<pagination>组件的三行最小用法、激活页与上一页/下一页按钮禁用状态的判定规则,以及这些行为在 pagination.component.ts 与 pagination.component.html 中是如何落地的,并了解对应单元测试的验证方式。
场景概述:Basic 示例要演示什么
该用例规格定义了一个非常聚焦的目标:向用户展示 Pagination 组件的基础分页功能。它的参与角色与范围如下:
- Primary Actor(主要参与者):User(用户)
- Scope(范围):Ngx-bootstrap DEMO,兼容 Bootstrap 版本 3 与 4
- Goal(目标):向用户展示基本的分页(basic pagination)功能
整个场景围绕"打开演示页 → 进入 Basic 子菜单 → 在不同页码间来回切换"展开,重点验证三件事:哪一页处于 active(激活)状态、上一页/下一页按钮何时禁用、以及页面切换后状态如何迁移。
主成功场景:用户与分页组件的完整交互路径
用例规格的 Main success scenario 描述了一条连贯的操作链路,这正是 Basic 示例被设计出来的初衷:
- 用户打开 Pagination 演示页面;
- 用户点击 Basic 子菜单;
- 默认情况下第 1 页处于激活状态,且 Previous(上一页)按钮为禁用状态;
- 用户点击第 3 页后,Next(下一页)按钮变为禁用,Previous 按钮变为可用,第 1 页取消激活,第 3 页变为激活;
- 用户再点击第 2 页后,Previous 与 Next 按钮均可用,第 2 页激活,其余页均不激活。
该规格还包含一条 Variations(变体)路径:用户可能不是通过点击子菜单,而是直接滚动(scroll)到 "Basic" 示例区块再开始操作,交互结果与主场景一致。
需要说明的是,用例规格中假设演示数据可以翻出 3 页,这一假设与 demo 实际配置完全吻合:Basic 示例设置totalItems = 30,而分页组件默认itemsPerPage = 10,30 条数据恰好分成 3 页(见下文"最小可运行示例"与"页数计算"小节)。
最小可运行示例:Basic 示例的真实代码
Basic 示例在文档站中的实现位于 libs/doc-pages/pagination/src/lib/demos/basic/ 目录,组件定义与模板各只有几行:
组件类 basic.ts:
import { Component } from '@angular/core'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector selector: 'demo-pagination-basic', templateUrl: './basic.html', standalone: false }) export class DemoPaginationBasicComponent {}模板 basic.html:
<pagination [totalItems]="30"></pagination>也就是说,只需一个属性绑定,就能获得一个可交互的完整分页条:totalItems传入总数据条数 30,其余全部走默认配置。该示例组件被注册进文档站的演示列表 pagination-section.list.ts,同时出现在 "Overview" 与 "Examples" 两个分区的 Basic 条目中(anchor 分别为basic与basic-ex)。
在自己项目中复用时,可以通过两种方式引入分页模块(见 usage.md):
// Standalone 组件用法 import { PaginationModule } from 'ngx-bootstrap/pagination'; @Component({ standalone: true, imports: [PaginationModule, ...] }) export class AppComponent {}// 传统 NgModule 用法 import { PaginationModule } from 'ngx-bootstrap/pagination'; @NgModule({ imports: [PaginationModule, ...] }) export class AppModule {}PaginationModule 内部同时导出PaginationComponent与PagerComponent,因此上述引入即可同时使用<pagination>和<pager>两个组件。
默认行为背后的源码实现
Basic 场景中"第 1 页激活、Previous 禁用"以及"点到最后、Next 禁用"都不是魔法,而是由 pagination.component.ts 中的几个核心方法直接驱动的。
页数计算:为什么 30 条数据正好是 3 页
组件的totalItemsInput与itemsPerPageInput是两个被effect监听响应式输入,任一变化都会触发总页数重算(pagination.component.ts)。页数计算逻辑在calculateTotalPages()(pagination.component.ts):
protected calculateTotalPages(): number { const totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil(this.totalItems / this.itemsPerPage); return Math.max(totalPages || 0, 1); }将 Basic 示例的数值代入:Math.ceil(30 / 10) = 3,于是渲染出 1、2、3 三个页码。注意两个边界规则:当itemsPerPage < 1时,所有条目会被合并到一页;计算结果至少为 1,保证空数据时也有一条可展示的分页条。
激活页与按钮禁用判定
Previous / Next 按钮是否禁用,由两个方法决定(pagination.component.ts):
noPrevious(): boolean { return this.page === 1; } noNext(): boolean { return this.page === this.totalPages; }- 当
page === 1(第 1 页)时,Previous 禁用——这正是用例规格第 3 步描述的状态; - 当
page === totalPages(最后一页,本例为第 3 页)时,Next 禁用——对应规格第 4 步"点击第 3 页后 Next 禁用"; - 处于中间页(第 2 页)时,两个条件均不成立,两个按钮同时可用——对应规格第 5 步。
模板中的渲染逻辑位于 pagination.component.html 与 pagination.component.html:方向按钮通过[class.disabled]="noPrevious() || disabled()"/[class.disabled]="noNext() || disabled()"挂上 Bootstrap 的.disabled类;页码链接则通过[class.active]="pg.active"标记当前激活页。按钮点击统一走selectPage(page - 1, $event)/selectPage(page + 1, $event)。
点击翻页的核心方法
selectPage()是用户每次点击页码或方向按钮时都会命中的入口(pagination.component.ts):
selectPage(page: number, event?: Event): void { if (event) { event.preventDefault(); } if (!this.disabled()) { if (event && event.target) { const target: any = event.target; target.blur(); } this.writeValue(page); this.onChange(this.page); } }它做了三件事:阻止<a href>的默认跳转、让被点击的链接失焦(避免残留焦点样式)、写入新页码。writeValue()进而触发page的 setter(pagination.component.ts),setter 中有一个值得注意的防越界逻辑:this._page = value > this.totalPages ? this.totalPages : value || 1——即使点击超出范围的页码也不会越界,会被钳制在最后一页;若新旧页码不同,则向外发出pageChanged事件,携带{ page, itemsPerPage }。
页码列表的生成
渲染出的页码集合由getPages()生成(pagination.component.ts):默认情况下(未设置maxSize或maxSize >= totalPages)直接罗列 1 到总页数的全部页码,并用makePage(num, num.toString(), num === currentPage)为当前页标记active: true。当设置了maxSize且总页数超过该上限时,才会进入"只显示部分页码"的逻辑——rotate为true时当前页居中,否则按页组滚动并在组间插入...跳页链接。Basic 示例不涉及maxSize,因此 3 个页码会完整展示。
状态机推演:第 1 页 → 第 3 页 → 第 2 页
把用例规格的交互链路映射到上述源码,可以得到一份精确的状态迁移表(以 30 条数据、每页 10 条、共 3 页为例):
| 操作前状态 | 用户操作 | 操作后状态 | 对应源码依据 |
|---|---|---|---|
| 初始:page = 1 | 无(页面加载) | 第 1 页active;Previous 禁用;Next 可用 | noPrevious()为 true;makePage(1, '1', true) |
| 第 1 页 | 点击第 3 页 | 第 3 页active,第 1 页取消激活;Previous 可用;Next 禁用 | page = 3 = totalPages,noNext()为 true |
| 第 3 页 | 点击第 2 页 | 第 2 页active,其余不激活;Previous、Next 均可用 | page = 2位于 1 与 3 之间,两个判定均为 false |
这与用例规格第 3、4、5 步的预期完全一致。注意每步点击时pageChanged事件都会携带当前页号触发,方便外部监听(见下文"常用扩展")。
测试如何锁定这些默认行为
Basic 场景描述的行为并不是停留在文档层面的"愿望清单",而是有单元测试逐条锁定的。在 src/pagination/testing/pagination.component.spec.ts 中可以找到对应的验证:
- 默认值测试:设置
disabled = true后断言渲染出 3 个<li>(Previous、页码 1、Next),且首尾两个<li>都带disabled类(pagination.component.spec.ts); - 多页测试:
totalItems = 10, itemsPerPage = 4时断言总页数为 3、第 1 页激活、Previous 禁用、Next 可用(pagination.component.spec.ts); - 点击翻页测试(模拟规格操作):以
totalItems = 30, itemsPerPage = 4(共 8 页)模拟"点击第 5 页 → 第 5 页激活、Previous 变为可用"与"点击第 8 页 → Next 变为禁用、第 8 页激活"(pagination.component.spec.ts)。这与用例规格"点击最后一页 Next 禁用"的断言模式一致。
测试中还覆盖了带maxSize与boundaryLinks的点击场景(pagination.component.spec.ts),并验证了点击已禁用的 First 链接不会改变当前页。
常用扩展:从基础示例出发的进阶用法
Basic 示例刻意保持最简,而围绕它的更多行为可以平滑地扩展到相邻用例(同目录 docs/spec/pagination/ 下还有多份 use-case 文档):这里列出几个与基础交互直接相关的能力,均可在 libs/doc-pages/pagination/src/lib/demos/ 下找到对应演示。
双向绑定当前页与翻页事件
通过[(ngModel)]双向绑定当前页码,并用(pageChanged)监听翻页结果(page-changed-event.html):
<pagination [totalItems]="66" [(ngModel)]="currentPage" (pageChanged)="pageChanged($event)"></pagination>事件回调接收PageChangedEvent对象(page-changed-event.ts):
pageChanged(event: PageChangedEvent): void { this.page = event.page; }组件实现了ControlValueAccessor(pagination.component.ts),因此天然兼容 Angular 表单模型与ngModel双向绑定。
开关边界链接与方向链接
boundaryLinks = true时显示 First / Last 按钮(默认隐藏,见 boundary-links.html);directionLinks = false时隐藏 Previous / Next 按钮(默认显示,见 direction-links.html)。
对应模板逻辑在 pagination.component.html 与 pagination.component.html 中:边界与方向按钮都受各自@if守卫控制,且同样遵循noPrevious()/noNext()的禁用规则。
整体禁用
disabled = true时所有分页链接不可点击(disabled.html)。源码中selectPage()会在this.disabled()为真时直接返回(pagination.component.ts),模板中非激活页也统一挂上disabled类(pagination.component.html)。
自定义链接文本
基础场景之外,还可以通过previousText、nextText、firstText、lastText替换按钮文字,例如 custom-links-content.html 使用‹、›、«、»作为符号按钮。
默认配置速查
Basic 示例"零配置"即可工作的底气来自 PaginationConfig,组件构造时若未显式传配置,会调用configureOptions(paginationConfig.main)装载默认值(pagination.component.ts)。main配置如下:
main: Partial<ConfigModel> = { itemsPerPage: 10, boundaryLinks: false, directionLinks: true, firstText: 'First', previousText: 'Previous', nextText: 'Next', lastText: 'Last', pageBtnClass: '', rotate: true };这与 Basic 示例的渲染结果一一对应:每页 10 条(所以 30 条数据分为 3 页)、无 First/Last 按钮、显示 Previous/Next、按钮文字为英文默认值。完整的ConfigModel结构定义在 models/index.ts,同文件还定义了PagesModel(每个页链接的文本、页码与激活标记)以及模板定制所需的PaginationLinkContext/PaginationNumberLinkContext上下文接口。
小结
通过对 pagination.examples.basic.use-case.md 的逐条拆解可以看到:Basic 示例虽然只用了<pagination [totalItems]="30"></pagination>一行模板,但它完整承载了分页组件的三条核心契约——当前页永远只有一个是激活的、首尾页会分别禁用 Previous/Next、任意切换都实时更新激活态。这三条契约在 pagination.component.ts 的calculateTotalPages()、noPrevious()/noNext()、selectPage()与getPages()中逐行实现,并被 pagination.component.spec.ts 的点击测试逐一验证。掌握这个最小闭环,再向边界链接、方向链接、禁用、页数限制等扩展用例(docs/spec/pagination/ 下的其余 use-case 文档)推进,就能完整驾驭 ngx-bootstrap 的分页能力。
- UI组件
- 前端
【免费下载链接】ngx-bootstrap
Fast and reliable Bootstrap widgets in Angular (supports Ivy engine)
相关推荐
ngx-bootstrap 按钮组件实战:Disabled Buttons 禁用按钮示例深度解析
ngx bootstrap 按钮组件实战:Disabled Buttons 禁用按钮示例深度解析 本文以 ngx bootstrap 官方演示应用中的 Disa
UI组件前端ngx-bootstrap Timepicker 基础示例全解析:小时、分钟与 AM/PM 的默认交互行为
ngx bootstrap Timepicker 基础示例全解析:小时、分钟与 AM/PM 的默认交互行为 本指南以 ngx bootstrap 官方 spec
UI组件前端ngx-bootstrap Buttons 基础示例(Basic)使用场景与实现源码解析
ngx bootstrap Buttons 基础示例(Basic)使用场景与实现源码解析 ngx bootstrap 是 Angular 生态中基于 Boots
UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考