☰
ng-zorro-antd InputNumber 受控模式越界值:`out-of-range` 警告样式机制详解
2026/9/26 7:32:46 网站建设 项目流程
  • 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-input-number数字输入框中,当以受控模式(如ngModel)传入一个超出nzMin/nzMax范围的value时,组件不会强行把数据截断回边界,而是通过添加ant-input-number-out-of-range类输出警告样式,同时保持输入框中显示的值与业务层存储的数据完全一致。本文将基于该 demo(out-of-range.md)与底层源码,讲解这一机制的设计动机、判定逻辑、样式实现与测试验证,并给出可直接运行的完整示例,帮助你理解并驾驭受控模式下数字输入框的边界行为。

现象:demo 演示了什么

官方 demo(out-of-range.ts)的模板与逻辑非常简洁:

import { Component, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; @Component({ selector: 'nz-demo-input-number-out-of-range', imports: [FormsModule, NzInputNumberModule], template: `<nz-input-number [(ngModel)]="value" nzMin="1" nzMax="10" />` }) export class NzDemoInputNumberOutOfRangeComponent { readonly value = signal(99); }

其中value被初始化为99,而边界是nzMin="1"、nzMax="10"。由于采用的是[(ngModel)]受控绑定,组件拿到 99 这个越界值后,并不会自动回退到 10,而是原样显示,同时整个输入框落入「超出边界」的警告样式。

官方对这一 demo 的描述是:

当通过受控将value超出边界时,提供警告样式。 When thevalueis out of range in controlled mode, a warning style is provided.

核心机制:out-of-range类的判定源码

越界样式的落点并非模板中的某个分支,而是组件宿主元素上的一个动态类名。在 input-number.component.ts 中,inputNumberClass这个computed信号这样计算:

protected readonly inputNumberClass = computed(() => { return { 'ant-input-number': true, 'ant-input-number-lg': this.finalSize() === 'large', 'ant-input-number-sm': this.finalSize() === 'small', 'ant-input-number-disabled': this.finalDisabled(), 'ant-input-number-readonly': this.nzReadOnly(), 'ant-input-number-focused': this.focused(), 'ant-input-number-rtl': this.dir() === 'rtl', 'ant-input-number-in-form-item': !!this.nzFormStatusService, 'ant-input-number-out-of-range': this.value() !== null && !isInRange(this.value()!, this.nzMin(), this.nzMax()), ...getVariantClassNames('ant-input-number', this.finalVariant()), ...getStatusClassNames('ant-input-number', this.finalStatus(), this.hasFeedback()) }; });

关键一行是:

'ant-input-number-out-of-range': this.value() !== null && !isInRange(this.value()!, this.nzMin(), this.nzMax())

isInRange是同文件底部的工具函数(L599-L601):

function isInRange(value: number, min: number, max: number): boolean { return value >= min && value <= max; }

由此可以归纳判定规则:

  • value为null(空值)时,不会添加out-of-range类;
  • value合法且满足min <= value <= max时,不会添加;
  • value小于min或大于max时,添加ant-input-number-out-of-range类。

需要特别注意的是:判定依据是组件内部的value信号(由writeValue写入),因此只有受控模式下「真实传入的数值」越界才会触发;而用户在输入框里敲出尚未确认的越界文本时,走的是一条完全不同的路径(见下文「输入、失焦、步进按钮的边界行为」)。

设计动机:为什么受控模式允许越界

很多开发者会疑惑:既然nzMin/nzMax已经定义了边界,为什么受控模式下组件不把越界的值直接钳制回范围内?官方 FAQ(doc/index.en-US.md)给出了明确解释:

在受控模式下,开发者可能自己存储相关数据。如果组件把数据约束回范围内,会导致展示的数据与实际存储的数据不一致,进而在表单字段等场景下引发潜在的数据问题。

也就是说,nz-input-number在受控模式下遵循「数据以业务层为准」的原则:组件只是数据的「显示器 + 步进器」,不是数据的「纠偏器」。强行回退会造成表单底层值与界面显示脱节,而显式给出警告样式,既保留了数据原貌,又让用户感知到数值越界。

这一点在源码的writeValue实现中体现得最直接(L379-L385):

writeValue(value: number | null | undefined): void { if (isNil(value)) value = null; untracked(() => { this.value.set(value); this.setValue(value); }); }

writeValue原样保存并展示传入值,不做任何getRangeValue钳制。对比之下,getRangeValue(L609-L634)这个钳制工具只出现在失焦修正、步进计算等非受控写入路径中——这正是受控/非受控两条数据链路的边界所在。

输入、失焦、步进按钮的边界行为

越界样式只描述「受控写入」的状态,而用户交互时的越界处理遵循另一套规则,理解它们才能避免误用:

1. 键入越界值(不更新 value)

用户在输入框键入数字时走setValueByTyping(L461-L489),其中对范围做了拦截:

if (!isInRange(parsedValue, this.nzMin(), this.nzMax())) { return; } this.updateValue(parsedValue);

即:键入的解析值一旦越界,组件不会把它写入value(也就不会触发ngModelChange),界面上只保留用户键入的文本。这意味着「越界警告」只在受控数据本身越界时出现,不会在输入过程中闪烁。

2. 失焦修正(回到边界内)

当输入框失焦(blur)时,组件调用fixValue(L498-L525):

// fix range if (!isInRange(fixedValue, this.nzMin(), this.nzMax())) { fixedValue = getRangeValue(fixedValue, this.nzMin(), this.nzMax(), precision); } this.setValue(fixedValue);

失焦后越界的键入值会被getRangeValue修正回边界:小于min取min,大于max取max;同时结合nzPrecision做精度化处理(getRangeValue内部对边界做了Math.ceil/Math.floor的取整,具体方向取决于min/max的正负号,源码注释有详细示例)。

3. 步进按钮/键盘/滚轮(越界时直接忽略)

step方法在一开始就做了越界拦截(L410-L419):

// Ignore step since out of range if ((up && this.upDisabled()) || (!up && this.downDisabled())) { return; }

upDisabled/downDisabled(L331-L336)依据当前value与边界比较得出:

protected readonly upDisabled = computed(() => { return !isNil(this.value()) && this.value()! >= this.nzMax(); }); protected readonly downDisabled = computed(() => { return !isNil(this.value()) && this.value()! <= this.nzMin(); });

当受控value已越界(比如 99 且 max=10),向上步进必然被禁用;此时若调用step也会直接忽略,同时给上/下箭头加上ant-input-number-handler-up-disabled之类的禁用样式。

警告样式的落地:LESS 与颜色

ant-input-number-out-of-range类的视觉效果定义在 style/index.less:

// ===================== Out Of Range ===================== &-out-of-range { input { color: @error-color; } }

实现非常克制:仅仅把内部<input>的文字颜色染成错误色(@error-color),达到「警告」的可视化效果,而不改变边框、背景等其他外观。

这里需要与「状态样式」区分开:nzStatus(可选error或warning)对应的是ant-input-number-status-error/ant-input-number-status-warning类,由getStatusClassNames计算生成(相关样式见 style/status.less),主要作用于边框与反馈图标;而out-of-range是针对受控值越界的独立机制,两者可以同时出现、互不冲突。组件 API 中关于nzStatus的说明见 doc/index.en-US.md。

测试验证:越界类名确实生效

官方单元测试对这套机制做了双向验证(input-number.component.spec.ts):

it('should be apply out-of-range class', async () => { component.min.set(1); component.max.set(2); component.value = 3; fixture.detectChanges(); await updateNonSignalsInput(fixture); expect(hostElement.classList).toContain('ant-input-number-out-of-range'); component.value = 0; fixture.detectChanges(); await updateNonSignalsInput(fixture); expect(hostElement.classList).toContain('ant-input-number-out-of-range'); });

测试同时覆盖了「大于 max」与「小于 min」两个方向的越界场景:min=1, max=2时,受控值为 3(> max)或 0(< min),宿主元素nz-input-number上都会出现ant-input-number-out-of-range类,与组件源码中的判定表达式完全一致。

完整可用示例与使用建议

把官方 demo 稍作扩展,可以得到一个可运行、可交互的完整示例:

import { Component, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; @Component({ selector: 'app-out-of-range-demo', imports: [FormsModule, NzInputNumberModule], template: ` <nz-input-number [(ngModel)]="value" nzMin="1" nzMax="10" [style.width.%]="100" /> <p>当前受控 value:{{ value() }}</p> ` }) export class OutOfRangeDemoComponent { readonly value = signal(99); }

使用建议:

  • 受控模式下:数据源本身可能越界(如历史数据、服务端下发的脏数据),此时组件会显示越界数值并给出警告色,你可以结合value()与nzMin/nzMax自行决定是否提交、是否二次校验,组件不会替你篡改数据;
  • 需要强制纠偏时:不要在ngModelChange里直接改回边界再写回(容易造成数据闪烁),更推荐在失焦回调或表单提交阶段,使用业务层自己的钳制逻辑处理;
  • 想要无越界的强约束体验:可以放弃受控绑定,依赖组件自身的键入拦截与失焦修正(setValueByTyping+fixValue),此时用户永远无法在交互层留下越界值,但展示值可能与你存储的数据短暂不一致;
  • 视觉定制:若默认的错误色不够醒目,可以在项目样式中覆盖.ant-input-number-out-of-range input的颜色,因为该选择器优先级与命名都非常明确。

小结

「超出边界」demo 揭示的不仅是样式本身,更是 ng-zorro-antd InputNumber 在受控模式下的数据哲学:展示忠实于数据,警告替代纠偏。ant-input-number-out-of-range类的判定、writeValue的原样写入、键入时的越界拦截、失焦时的边界修正,以及步进时的禁用逻辑,共同构成了一套自洽的边界行为模型。掌握这条机制,你在处理表单回显、服务端脏数据、边界校验等场景时,就能准确预判组件的每一步表现。

  • UI组件
  • 前端

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

Angular UI Component Library based on Ant Design

项目地址:https://gitcode.com/gh_mirrors/ng/ng-zorro-antd
点击查看免费下载
上一篇:Subtitle Edit语音转文字教程:用Whisper等本地引擎免费自动生成字幕
下一篇:cool-admin(midway版)后端服务注册:设计与实践

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询