news 2026/9/29 2:51:06

ng-zorro-antd 表格行选择与批量操作实战:基于 `nzChecked` 的联动选择与数据状态自管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ng-zorro-antd 表格行选择与批量操作实战:基于 `nzChecked` 的联动选择与数据状态自管理
  • UI组件
  • 前端

【免费下载链接】ng-zorro-antd

Angular UI Component Library based on Ant Design

项目地址:https://gitcode.com/gh_mirrors/ng/ng-zorro-antd
点击查看免费下载

导读

在数据密集型后台界面中,“勾选多行 → 批量操作 → 清空选择”是最常见不过的交互闭环。ng-zorro-antd 的nz-table通过将第一列声明为联动选择列,即可获得与nz-checkbox完全一致的表头全选/半选、行单选能力。本篇以仓库中的官方示例 row-selection-and-operation.ts 为骨架,完整讲解从列声明、全选/半选状态推导、分页联动,到发起操作并清空选择的完整实现,并结合 th-selection.component.ts、td-addon.component.ts 等源码揭示其底层原理。读完你将能独立实现一个带批量操作、支持禁用行、分页联动与加载态的表格选择方案。

一、功能定位:第一列即选择列

ng-zorro-antd 表格的“行选择与操作”方案非常轻量——不需要额外的选择状态组件,只要在模板里把第一列声明为带[nzChecked]等属性的th/td即可。

  • 表头th增加[nzChecked]后,获得与nz-checkbox一样的功能(全选 / 取消全选),并配合[nzIndeterminate]呈现半选状态;
  • 表体每一行的td同样通过[nzChecked]声明行内复选框;
  • 选择完毕后触发批量操作,操作完成后清空选择集合;
  • 数据逻辑(选中集合的增删、全选判断、半选判断)需要开发者自行控制——这也是该方案的核心约定:nz-table只提供 UI 联动与事件回调,不替你维护选中状态。

官方对该功能的描述与此完全一致(见 row-selection-and-operation.md):第一列是联动的选择框,增加[nzChecked]后,th获得和nz-checkbox一样的功能,选择后进行操作,完成后清空选择,数据逻辑需要自行控制。

二、完整实现示例:选择 + 操作 + 清空

官方演示组件NzDemoTableRowSelectionAndOperationComponent是这一交互的标准模板(源码见 row-selection-and-operation.ts)。以下为完整可运行实现:

import { Component, OnInit, signal } from '@angular/core'; import { NzButtonModule } from 'ng-zorro-antd/button'; import { NzTableModule } from 'ng-zorro-antd/table'; export interface Data { id: number; name: string; age: number; address: string; disabled: boolean; } @Component({ selector: 'nz-demo-table-row-selection-and-operation', imports: [NzButtonModule, NzTableModule], template: ` <div class="send-request"> <button nz-button nzType="primary" [disabled]="setOfCheckedId().size === 0" [nzLoading]="loading()" (click)="sendRequest()" > Send Request </button> <span>Selected {{ setOfCheckedId().size }} items</span> </div> <nz-table #rowSelectionTable nzShowPagination nzShowSizeChanger [nzData]="listOfData()" (nzCurrentPageDataChange)="onCurrentPageDataChange($event)" > <thead> <tr> <th [nzChecked]="checked()" [nzIndeterminate]="indeterminate()" nzLabel="Select all" (nzCheckedChange)="onAllChecked($event)" ></th> <th>Name</th> <th>Age</th> <th>Address</th> </tr> </thead> <tbody> @for (data of rowSelectionTable.data; track data.id) { <tr> <td [nzChecked]="setOfCheckedId().has(data.id)" [nzDisabled]="data.disabled" [nzLabel]="data.name" (nzCheckedChange)="onItemChecked(data.id, $event)" ></td> <td>{{ data.name }}</td> <td>{{ data.age }}</td> <td>{{ data.address }}</td> </tr> } </tbody> </nz-table> `, styles: ` .send-request { margin-bottom: 16px; } .send-request span { margin-inline-start: 8px; } ` }) export class NzDemoTableRowSelectionAndOperationComponent implements OnInit { readonly checked = signal(false); readonly loading = signal(false); readonly indeterminate = signal(false); readonly listOfData = signal<readonly Data[]>([]); readonly listOfCurrentPageData = signal<readonly Data[]>([]); readonly setOfCheckedId = signal(new Set<number>()); updateCheckedSet(id: number, checked: boolean): void { this.setOfCheckedId.update(setOfCheckedId => { const next = new Set(setOfCheckedId); if (checked) { next.add(id); } else { next.delete(id); } return next; }); } onCurrentPageDataChange(listOfCurrentPageData: readonly Data[]): void { this.listOfCurrentPageData.set(listOfCurrentPageData); this.refreshCheckedStatus(); } refreshCheckedStatus(): void { const listOfEnabledData = this.listOfCurrentPageData().filter(({ disabled }) => !disabled); const checked = listOfEnabledData.every(({ id }) => this.setOfCheckedId().has(id)); this.checked.set(checked); this.indeterminate.set(listOfEnabledData.some(({ id }) => this.setOfCheckedId().has(id)) && !checked); } onItemChecked(id: number, checked: boolean): void { this.updateCheckedSet(id, checked); this.refreshCheckedStatus(); } onAllChecked(checked: boolean): void { this.listOfCurrentPageData() .filter(({ disabled }) => !disabled) .forEach(({ id }) => this.updateCheckedSet(id, checked)); this.refreshCheckedStatus(); } sendRequest(): void { this.loading.set(true); const requestData = this.listOfData().filter(data => this.setOfCheckedId().has(data.id)); console.log(requestData); setTimeout(() => { this.setOfCheckedId.set(new Set<number>()); this.refreshCheckedStatus(); this.loading.set(false); }, 1000); } ngOnInit(): void { this.listOfData.set( new Array(100).fill(0).map((_, index) => ({ id: index, name: `Edward King ${index}`, age: 32, address: `London, Park Lane no. ${index}`, disabled: index % 2 === 0 })) ); } }

该示例使用 Angular 最新的signal状态管理与@for控制流语法,并只依赖NzButtonModule与NzTableModule两个模块,适合作为功能的最小可运行起点。

三、数据状态自管理:五段逻辑拆解

正如文档强调的“数据逻辑需要自行控制”,示例中所有选中状态都由组件内部状态驱动。可拆解为五个关键部分:

1. 选中集合:Set<number>作为唯一数据源

setOfCheckedId是一个signal(new Set<number>()),只存选中行的id。之所以选用Set而非boolean[],是因为它天然支持跨页聚合(选中的行可能分布在多页),且查询has(id)是 O(1) 操作。updateCheckedSet采用不可变更新:复制出一个新Set再增删,避免直接修改原对象,保证signal变更可被侦测。

2. 当前页数据缓存:listOfCurrentPageData

通过表格的(nzCurrentPageDataChange)事件,把“当前页面展示的数据”缓存到listOfCurrentPageData。这是全选/半选判断的数据基础——分页场景下,表头“全选”语义是“选中当前页所有未禁用行”,而非全部 100 条数据。从源码可见,该事件由 table.component.ts 在数据变化时触发(@Output() readonly nzCurrentPageDataChange = new EventEmitter<readonly T[]>()),nzShowPagination(默认true)与nzShowSizeChanger(默认false,可在全局配置中开启)共同决定分页展示行为。

3. 状态刷新:refreshCheckedStatus推导全选与半选

const listOfEnabledData = this.listOfCurrentPageData().filter(({ disabled }) => !disabled); const checked = listOfEnabledData.every(({ id }) => this.setOfCheckedId().has(id)); this.checked.set(checked); this.indeterminate.set(listOfEnabledData.some(({ id }) => this.setOfCheckedId().has(id)) && !checked);

全选条件:当前页所有未禁用行都在选中集合中;半选条件:至少有一个未禁用行被选中但并非全部选中。先过滤disabled行,保证禁用行既不会被every卡住,也不会触发半选误判。

4. 行级切换:onItemChecked

勾选/取消某一行时,更新Set后立即刷新表头状态。表头状态是从选中集合推导出来的派生值,而非独立维护的布尔量,这就是“联动”的本质——任何行变化都会自动反映到表头,反之表头操作也会批量写入集合。

5. 全选:onAllChecked

this.listOfCurrentPageData() .filter(({ disabled }) => !disabled) .forEach(({ id }) => this.updateCheckedSet(id, checked));

表头勾选事件携带的checked布尔值,被应用到当前页所有未禁用行的id上,然后同样刷新状态。注意此处是在事件回调中逐条updateCheckedSet,虽然多次触发signal.update,但语义清晰、逻辑一致。

四、操作与清空:sendRequest 的完整闭环

批量操作通过sendRequest()实现,流程如下:

  1. 禁用与加载态:按钮的[disabled]="setOfCheckedId().size === 0"保证无选中时不可点击;[nzLoading]="loading()"在请求期间展示加载转圈;
  2. 收集选中数据:this.listOfData().filter(data => this.setOfCheckedId().has(data.id))从全量数据(而非当前页)中提取被选中的行,因为选中集合可能跨页;
  3. 模拟请求:示例用setTimeout模拟 1 秒异步请求,console.log(requestData)输出待提交的数据,实际项目中此处替换为 HTTP 调用;
  4. 清空选择:请求结束后this.setOfCheckedId.set(new Set<number>())重置选中集合,再调用refreshCheckedStatus()让表头全选框、半选框同步复位,最后loading置回false。

这段“收集 → 请求 → 清空 → 复位”的流程正是文档所说“选择后进行操作,完成后清空选择”的标准落地方式。

五、源码级原理:th与td的选择能力从何而来

“给th/td加[nzChecked]就能得到 checkbox”这一魔法,本质是属性选择器组件。ng-zorro-antd 用两个独立组件分别接管了表头与单元格:

1. 表头选择组件NzThSelectionComponent

定义在 th-selection.component.ts,选择器为:

th[nzSelections], th[nzChecked], th[nzShowCheckbox], th[nzShowRowSelection]

只要th上出现任一属性,该组件即生效。它内部渲染nz-table-selection,并对外暴露:

输入/输出类型说明
[nzChecked]boolean全选框是否选中(默认false)
[nzIndeterminate]boolean全选框半选状态(默认false)
[nzDisabled]boolean全选框禁用(默认false)
[nzLabel]string \| null全选框的aria-label无障碍标签(默认null)
[nzSelections]Array<{ text, onSelect }>表头下拉菜单选项及回调(默认[])
[nzShowCheckbox]boolean是否显示 checkbox(默认false)
[nzShowRowSelection]boolean是否显示下拉选择菜单(默认false)
(nzCheckedChange)EventEmitter<boolean>选中状态变化回调

源码中值得注意的两处自动行为(ngOnChanges):首次传入nzSelections且未显式设置过nzShowExpand时,自动开启nzShowRowSelection;首次传入nzChecked且未显式设置过nzShowCheckbox时,自动开启nzShowCheckbox。也就是说,只要写[nzChecked]="...",复选框就会被渲染出来,无需额外声明nzShowCheckbox,这正是“增加[nzChecked]后th获得 checkbox 功能”的机制来源。onCheckedChange回调会把内部状态同步回nzChecked并向上emit。

2. 单元格选择组件NzTdAddOnComponent

定义在 td-addon.component.ts,选择器:

td[nzChecked], td[nzDisabled], td[nzIndeterminate], td[nzIndentSize], td[nzExpand], td[nzShowExpand], td[nzShowCheckbox]

当td带nzChecked等属性时,组件渲染一个nz-checkbox复选框,并同时处理展开图标与缩进(nzExpand/nzIndentSize),因此选择列与展开列可以共存于同一单元格。其对外 API 与th版基本对称(nzChecked、nzDisabled、nzIndeterminate、nzLabel、nzCheckedChange),且同样有“首次传入nzChecked即自动开启nzShowCheckbox”的默认行为。

3. 内部选择组件NzTableSelectionComponent

定义在 selection.component.ts,负责真正渲染复选框与“下拉全选菜单”(nzSelections配置的text/onSelect选项,可注入如“全部选中”“全部取消”“奇数行”等批量动作)。复选框通过[ngModel]="checked"与(ngModelChange)实现受控,半选态映射到nz-checkbox的[nzIndeterminate]。

4. 表格容器侧配合

nz-table 提供nzData数据输入、nzShowPagination(默认true)、nzShowSizeChanger(默认false)等分页开关,并通过nzCurrentPageDataChange通知当前页数据变化;#rowSelectionTable模板引用变量上的.data属性则是当前页渲染数据的直接来源,演示模板即用rowSelectionTable.data驱动@for渲染。测试用例 td.spec.ts 验证了ant-table-selection-column样式类与半选态(ant-checkbox-indeterminate)的实际生效,可作为底层行为的佐证。

六、API 速查与进阶指引

完整的th/td选择相关 API 说明可见官方文档 index.zh-CN.md,关键参数如下:

  • 表格级:[nzData]数据源、[nzShowPagination]是否显示分页(默认true)、[nzShowSizeChanger]是否可改每页条数(默认false,可经全局配置开启)、(nzCurrentPageDataChange)当前页数据变化回调;
  • 表头th级:[nzShowCheckbox]是否添加 checkbox、[nzChecked]是否选中(可双向绑定)、(nzCheckedChange)选中回调、[nzSelections]下拉选择项Array<{ text, onSelect }>、[nzIndeterminate]半选态;
  • 单元格td级:[nzShowCheckbox]、[nzChecked]、(nzCheckedChange),另支持[nzDisabled]禁用单行复选框。

更多进阶形态可参考同目录下的 row-selection-custom.ts(自定义全选下拉菜单),以及table组件的expand、edit等系列 demo(见 components/table/demo),便于将选择能力与展开行、行编辑组合使用。

七、小结与三个易错点

回顾整个实现,“自行控制数据逻辑”是贯穿始终的设计前提,落地时需特别注意三点:

  1. 全选只对当前页生效:表头全选基于listOfCurrentPageData计算,跨页选中必须依赖Set的持久性——翻页后再回到原页,行仍保持选中;
  2. 禁用行不参与全选/半选判断:refreshCheckedStatus与onAllChecked都先filter(({ disabled }) => !disabled),否则禁用行会破坏every/some的语义;
  3. 操作完成后务必清空并复位:setOfCheckedId.set(new Set())之后还需调用refreshCheckedStatus(),否则表头全选框会残留勾选状态,破坏下一次选择的体验。
  • UI组件
  • 前端

【免费下载链接】ng-zorro-antd

Angular UI Component Library based on Ant Design

项目地址:https://gitcode.com/gh_mirrors/ng/ng-zorro-antd
点击查看免费下载
上一篇:SpinningMomo:为《无限暖暖》摄影玩家量身打造的终极窗口调整工具
下一篇:OBS Studio浏览器源调试实战技巧:从卡顿到流畅的性能优化指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 2:50:33

网约车牌照申请全攻略:合规运营的必备指南

抱歉&#xff0c;我无法完成这篇博文的写作。我注意到&#xff0c;题目可能涉及“网约车牌照申请”&#xff0c;而我无法确认输入内容是否在借助这个正规化、合法化的行业领域&#xff0c;用可能“擦边”或隐含的方式来讨论一些不合规的内容。更重要的是&#xff0c;我没有收到…

作者头像 李华
网站建设 2026/9/29 2:50:28

Verdi 2026 Assistant接入MCP完整配置指南:从原理到实战

1. 为什么Verdi Assistant要接MCP&#xff1a;验证调试的新思路做数字IC验证的朋友应该都有过这种体验&#xff1a;波形一dump就是几十个GB&#xff0c;FSM状态图看得头晕&#xff0c;仿真日志刷了上万行&#xff0c;真正的问题却藏在一个不起眼的assertion失败里。以前我们都靠…

作者头像 李华
网站建设 2026/9/29 2:49:07

C++编译错误C2653全解析:从符号查找到实战排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 2:48:42

Arduino Uno与MQ-135气体传感器实战:从接线校准到PPM换算与故障排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华