- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
导读
本文将围绕 ng-zorro-antd(Angular UI Component Library based on Ant Design)中 Modal 弹窗的页脚定制能力展开,以仓库中components/modal/demo/footer2这一官方示例(对应文档 footer2.md)为骨架,完整讲解nzModalFooter指令的使用方法:既可以在页面模板中直接声明式定制页脚按钮,也可以配合NzModalService.create在动态创建的组件内部完成自定义。读完本文,你将掌握自定义 Modal 页脚的两种标准姿势、nzFooter的三种取值形态,以及按钮 loading 状态与关闭回调的底层实现原理,可直接复用到自己的业务弹窗中。
一、为什么要自定义页脚
ng-zorro-antd 的 Modal 默认自带一套「取消 / 确定」按钮,二者分别通过nzCancelText、nzOkText控制文案,nzOkType、nzOkDanger控制确定按钮的样式,点击后触发nzOnCancel、nzOnOk事件。但当业务需要在页脚放三四个按钮、带自定义样式的按钮、或与表单联动时,默认按钮就不够用了。
为此,ng-zorro-antd 提供了三种页脚定制手段(定义见 modal-types.ts):
| 取值 | 类型 | 说明 |
|---|---|---|
nzFooter | string | 一段 HTML 字符串,直接渲染到页脚 |
nzFooter | TemplateRef<{}> | 一个模板引用,*nzModalFooter指令本质就是把它注入到nzFooter |
nzFooter | Array<ModalButtonOptions<T>> | 按钮配置对象数组,由内置页脚组件循环渲染成nz-button |
nzFooter | null | 完全不渲染页脚区域(注意是null而不是undefined) |
其中「模板引用」形态正是本文主角nzModalFooter指令的底层机制。
二、nzModalFooter指令的原理
先看指令本身(modal-footer.directive.ts):
@Directive({ selector: '[nzModalFooter]', exportAs: 'nzModalFooter' }) export class NzModalFooterDirective { public readonly templateRef: TemplateRef<{}> = inject(TemplateRef); private nzModalRef = inject(NzModalRef, { optional: true }); constructor() { this.nzModalRef?.updateConfig({ nzFooter: this.templateRef }); } }它的工作方式很巧妙:*nzModalFooter语法糖展开后,指令构造时会通过NzModalRef.updateConfig({ nzFooter: this.templateRef }),把自己所在元素编译出的TemplateRef直接写进当前弹窗的nzFooter配置。也就是说,指令内部并不维护任何页脚 UI,UI 渲染仍由弹窗容器完成(见 modal-container.component.ts):
@if (config.nzFooter !== null) { <div nz-modal-footer [modalRef]="modalRef" (cancelTriggered)="onCloseClick()" (okTriggered)="onOkClick()"></div> }只有当nzFooter为null时才完全跳过页脚容器;自定义页脚本质上是「复用容器、替换按钮区内容」。
同理,如果使用组件模板形式(<nz-modal>标签内嵌*nzModalFooter),NzModalComponent还会通过@ContentChild(NzModalFooterDirective, { static: true, read: TemplateRef })在setFooterWithTemplate里把模板同步到nzFooter,并在弹窗已创建时于下一个 tick 调用updateConfig热更新(见 modal.component.ts)。
内置的页脚组件 modal-footer.component.ts 渲染逻辑为:nzFooter是模板 → 用nzStringTemplateOutlet展开;是字符串 →innerHTML输出;是按钮数组 → 循环渲染nz-button;若nzFooter为空 → 回退渲染默认的取消/确定按钮(受nzCancelText、nzOkText是否null控制)。这解释了为什么自定义页脚时默认按钮不再出现:nzFooter已被替换为你的模板。
三、场景一:在页面模板中声明式自定义页脚
这是官方示例footer2的第一种用法(完整代码见 footer2.ts),直接在模板中放一个<nz-modal>,用[(nzVisible)]双向绑定控制显隐:
<button nz-button nzType="primary" (click)="showModal1()"> <span>In Template</span> </button> <nz-modal [(nzVisible)]="isVisible" nzTitle="Custom Modal Title" (nzOnCancel)="handleCancel()"> <div *nzModalContent> <p>Modal Content</p> <!-- 弹窗内容 --> </div> <div *nzModalFooter> <span>Modal Footer:</span> <button nz-button nzType="default" (click)="handleCancel()">Custom Callback</button> <button nz-button nzType="primary" (click)="handleOk()" [nzLoading]="isConfirmLoading()">Custom Submit</button> </div> </nz-modal>配套的组件逻辑(使用 Angular 新式signal状态):
readonly isVisible = signal(false); readonly isConfirmLoading = signal(false); showModal1(): void { this.isVisible.set(true); } handleOk(): void { this.isConfirmLoading.set(true); setTimeout(() => { this.isVisible.set(false); this.isConfirmLoading.set(false); }, 3000); } handleCancel(): void { this.isVisible.set(false); }要点:
*nzModalContent与*nzModalFooter是配套使用的内容指令,把弹窗主体与页脚从同一份模板中切分出去;- 自定义页脚后,「取消」按钮的
handleCancel()直接调用isVisible.set(false)关闭弹窗(等价于NzModalComponent.close()),「提交」按钮通过[nzLoading]绑定的isConfirmLoading()信号模拟 3 秒的异步提交 loading 状态,完成后自动关闭——这是官方 footer 示例(footer.ts)中用setTimeout模拟 1 秒 loading 的进阶版; - 不需要
NzModalService参与,NzModalComponent内部会自动modal.create(config)并持有NzModalRef(见 modal.component.ts)。
四、场景二:在动态组件内部自定义页脚
当用NzModalService.create以「传组件」的方式创建弹窗时,无法直接在调用方模板里写*nzModalFooter,此时把页脚模板放进内容组件自身即可。官方示例footer2的第二种用法正是如此:
import { NzModalModule, NzModalRef, NzModalService } from 'ng-zorro-antd/modal'; // 调用方 showModal2(): void { this.modalService.create({ nzTitle: 'Modal Title', nzContent: NzModalCustomFooterComponent }); } // 内容组件(自身携带页脚模板) @Component({ selector: 'nz-modal-custom-footer-component', imports: [NzButtonModule, NzModalModule], template: ` <div> <p>Modal Content</p> <!-- 弹窗内容 --> </div> <div *nzModalFooter> <button nz-button nzType="default" (click)="destroyModal()">Custom Callback</button> <button nz-button nzType="primary" (click)="destroyModal()">Custom Submit</button> </div> ` }) export class NzModalCustomFooterComponent { private readonly modalRef = inject(NzModalRef); destroyModal(): void { this.modalRef.destroy(); } }这里的关键差异:
- 注入
NzModalRef控制关闭:服务式创建的内容组件可以通过inject(NzModalRef)拿到当前弹窗的引用。注意 modal-footer.directive.ts 中NzModalRef是optional: true注入——模板场景下它可选用,而组件场景下指令构造时updateConfig会正常写回页脚配置; destroy()与close()等价:NzModalRef.destroy(result?)内部就是调用close(result)(见 modal-ref.ts),close会先置状态为CLOSING、分离 backdrop 并播放退场动画,动画结束后由_finishDialogClose()置为CLOSED并释放 overlay(modal-ref.ts)。因此示例中两个按钮共用destroyModal()是安全且规范的;- 数据与结果的传递:若需要把内容组件的数据带回调用方,
create({ nzContent, nzData })后可通过NzModalRef.getContentComponent()访问组件实例,或在afterCloseObservable 中接收close(result)传入的结果。
五、进阶:ModalButtonOptions数组形态
如果不想写模板,nzFooter还支持按钮配置数组,适合「少代码、纯配置」的场景。完整接口定义见 modal-types.ts:
export interface ModalButtonOptions<T = NzSafeAny> { label: string; // 必填,按钮文字 type?: NzButtonType; // 按钮类型,如 'primary' | 'default' | 'dashed' 等 danger?: boolean; // 危险按钮样式 shape?: NzButtonShape; // 按钮形状 ghost?: boolean; // 幽灵按钮 size?: NzButtonSize; // 尺寸 autoLoading?: boolean; // 默认 true:onClick 返回 Promise 时自动进入 loading show?: boolean | ((this, instance?) => boolean); // 是否显示,支持函数按内容组件实例动态计算 loading?: boolean | ((this, instance?) => boolean); // 手动 loading,与 autoLoading 不可同时用 disabled?: boolean | ((this, instance?) => boolean); // 是否禁用,支持函数 onClick?(this, contentComponentInstance?): NzSafeAny | Promise<NzSafeAny>; // 点击回调,可返回 Promise }内置页脚组件渲染时会为缺失字段合并默认值(type: null、size: 'default'、autoLoading: true、show: true、loading: false、disabled: false,见 modal-footer.component.ts),并支持把show/loading/disabled声明为函数,在渲染时以内容组件实例为参数动态求值(getButtonCallableProp)。点击时若onClick返回 Promise 且autoLoading为 true,会先自动置loading再在 resolve/reject 后复位(modal-footer.component.ts)。
与之对应,默认按钮的异步行为也有同样保证:NzModalRef.trigger中若nzOnOk/nzOnCancel返回 Promise,会自动切换nzOkLoading/nzCancelLoading,await 结束后仅当结果不为false才关闭弹窗(modal-ref.ts)——这就是官方 footer 示例「提交后进入 loading,完成后关闭」的底层实现。
六、如何彻底隐藏页脚
若完全不需要页脚(例如纯展示型弹窗),不要把nzFooter留空,而应显式设为null:
// 服务式 this.modalService.create({ nzContent: ..., nzFooter: null }); // 模板式 <nz-modal [nzFooter]="null" ...></nz-modal>由 modal-container.component.ts 的@if (config.nzFooter !== null)可知:null会跳过整个页脚容器;而undefined/空模板会让内置页脚组件走@else分支回退渲染默认的取消/确定按钮(前提是nzCancelText/nzOkText不为null)。这也是 footer 官方文档(footer.md)特别强调「You could setnzFootertonullif you don't need default footer buttons」的原因。
七、最佳实践小结
- 模板内嵌弹窗(
<nz-modal>+*nzModalFooter):适合页面局部弹窗,状态用signal或普通字段维护,(click)回调直接操作状态即可; - 服务式动态弹窗(
NzModalService.create+ 内容组件内*nzModalFooter):适合全局弹窗、复用组件,关闭务必通过inject(NzModalRef)调用destroy()/close(),不要反向依赖调用方; - 异步提交:给提交按钮绑定
[nzLoading],或让回调返回 Promise 配合autoLoading,避免重复点击;loading 期间NzModalRef也会自动屏蔽 backdrop 点击关闭与 ESC 关闭(modal-ref.ts); - 完全隐藏页脚用
nzFooter: null,而不是留空或删掉页脚标签; - 相关实现与示例源码均位于仓库
components/modal/目录下:指令 modal-footer.directive.ts、内置页脚组件 modal-footer.component.ts、配置类型 modal-types.ts、弹窗引用 modal-ref.ts、示例代码 footer2.ts 与 footer.ts,可对照阅读。
- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
相关推荐
ng-zorro-antd 弹窗页脚定制完全指南:nzFooter 属性与 nzModalFooter 指令实战解析
ng zorro antd 弹窗页脚定制完全指南:nzFooter 属性与 nzModalFooter 指令实战解析 在 ng zorro antd 中, nz
UI组件前端ng-zorro-antd Drawer 组件实战指南:模板驱动与服务式两种用法及完整 API 解析
ng zorro antd Drawer 组件实战指南:模板驱动与服务式两种用法及完整 API 解析 Drawer(抽屉)是 ng zorro antd 中一类
UI组件前端ng-zorro-antd Avatar 头像组件实战:三种尺寸与两种形状的完整配置指南
ng zorro antd Avatar 头像组件实战:三种尺寸与两种形状的完整配置指南 本篇技术指南以 ng zorro antd 官方演示 basic 为核
UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考