Signal Forms should not enforce native `min` / `max` / `minLength` / `maxLength` types on custom controls
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 54/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- angular, typescript
- 领域
- frontend
调研方向
从 packages/forms/signals/src/api/control.ts 中 min、max、minLength 和 maxLength 的定义开始,然后使用链接的 StackBlitz 示例复现类型错误。在决定应如何处理 bindings 之前,查看相关 issue #65676 以及现有的 custom-control 类型定义。完成标准是:类型不同的输入可以在 custom controls 中与 formField 一起使用,且不会出现这些编译时错误,同时 native-control 的行为仍然得到理解并由相关测试覆盖。
由索引模型根据 Issue 内容生成。
描述
Which @angular/* package(s) are the source of the bug?
forms
Is this a regression?
No
Description
Signal Forms type-check the min / max / minLength / maxLength inputs of a control against the native HTML meaning of those names:
For a native <input> this is sound: min really is "the smallest acceptable value" and minLength really is "the smallest number of characters".
For a custom control it is an assumption the framework cannot verify. Component libraries have had inputs with these names for years — since long before Signal Forms existed — and there is no reason why a minLength input on someone's component has to mean the same thing as minLength on <input>. Today that assumption is enforced at compile time, so a control whose min / minLength means something else simply cannot be used with [formField] — even when the schema declares no min() / minLength() rule at all.
Two real examples from Taiga UI:
1. Range / InputRange — a slider with two thumbs. Its value is readonly [number, number], but min / max are the bounds of the scale, and they are plain numbers:
export class TuiInputRange implements FormValueControl<readonly [number, number]> {
min = input(0);
max = input(100);
}
<!-- Type 'readonly [number, number] | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'. -->
<tui-input-range [formField]="f.range" />
~~~~~~~~~
Signal Forms require them to accept readonly [number, number] | undefined — a pair where the control needs a single number.
2. InputDateRange — a date range picker. Its minLength is the minimal length of the range (3 days, 2 months, …), not a number of characters:
// type TuiDayLike = {day?: number; month?: number; year?: number}
minLength = input<TuiDayLike | null>(null);
min = input<TuiDay>(TUI_FIRST_DAY); // earliest selectable day
<!--
[min] / [max] props:
Type 'TuiDayRange | undefined' is not assignable to type 'TuiDay | null'.
Type 'undefined' is not assignable to type 'TuiDay | null'.
[minLength] / [maxLength] props:
Type 'number | undefined' is not assignable to type 'TuiDayLike | null'.
Type 'undefined' is not assignable to type 'TuiDayLike | null'.
-->
<tui-textfield>
<input tuiInputDateRange [formField]="f.dates" />
~~~~~~~~~
</tui-textfield>
Signal Forms require minLength to accept number | undefined and min to accept NonNullable<TValue> | undefined, i.e. TuiDayRange | undefined — the whole range as the minimum, which is meaningless for this control.
Other examples across the library:
| control | its value type | its input means | what Signal Forms require input-prop to accept |
|---|---|---|---|
tuiTextarea |
string |
min / max: number of rows (number) |
string | undefined |
tuiInputDateMulti |
TuiDay[] |
min / max: selectable day range (TuiDay) |
TuiDay[] | undefined |
None of these can be "typed correctly" — the meanings do not overlap. The only way out is to accept the framework's type and throw the value away in a transform, which is exactly the kind of code the type system is supposed to prevent.
Proposed change
Do not type-check min / max / minLength / maxLength bindings for custom controls — accept whatever the control declares (e.g. unknown on the write side, the way InputSignalWithTransform<T, unknown> already does for the transform case), or make the binding opt-in so that a control states explicitly that its input carries the native meaning.
The runtime side deserves the same question, since the value is written into the input regardless of what it means there, but the type check is the part that blocks adoption today: it fails even for forms that never use these validators.
Please provide a link to a minimal reproduction of the bug
Please provide the exception or error you saw
<!-- Type 'readonly [number, number] | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'. -->
<tui-input-range [formField]="f.range" />
~~~~~~~~~
<!-- Type 'TuiDayRange | undefined' is not assignable to type 'TuiDay | null'. (min / max)
Type 'number | undefined' is not assignable to type 'TuiDayLike | null'. (minLength / maxLength) -->
<tui-textfield>
<input tuiInputDateRange [formField]="f.range" />
~~~~~~~~~
</tui-textfield>
Please provide the environment you discovered this bug in (run ng version)
Angular CLI : 22.0.7
Angular : 22.0.8
Node.js : 26.4.0
Package Manager : npm 11.17.0
Operating System : darwin arm64
┌───────────────────────────────────┬───────────────────┬───────────────────┐
│ Package │ Installed Version │ Requested Version │
├───────────────────────────────────┼───────────────────┼───────────────────┤
│ @angular-devkit/build-angular │ 22.1.3 │ 22.1.3 │
│ @angular-devkit/core │ 22.1.3 │ 22.1.3 │
│ @angular/build │ 22.0.7 │ 22.0.7 │
│ @angular/cdk │ 22.0.6 │ 22.0.6 │
│ @angular/cli │ 22.0.7 │ 22.0.7 │
│ @angular/common │ 22.0.8 │ 22.0.8 │
│ @angular/compiler │ 22.0.8 │ 22.0.8 │
│ @angular/compiler-cli │ 22.0.8 │ 22.0.8 │
│ @angular/core │ 22.0.8 │ 22.0.8 │
│ @angular/forms │ 22.0.8 │ 22.0.8 │
│ @angular/platform-browser │ 22.0.8 │ 22.0.8 │
│ @angular/platform-browser-dynamic │ 22.0.8 │ 22.0.8 │
│ @angular/router │ 22.0.8 │ 22.0.8 │
│ rxjs │ 7.8.2 │ 7.8.2 │
│ typescript │ 6.0.3 │ ~6.0.2 │
│ vitest │ 4.1.11 │ ^4.0.8 │
│ zone.js │ 0.15.1 │ ~0.15.0 │
└───────────────────────────────────┴───────────────────┴───────────────────┘
Anything else?
Related
This continues
where min / max were already relaxed once — from number to NonNullable<TValue> | undefined — for exactly this reason: an existing ControlValueAccessor could not be migrated without breaking its public min / max API.
- 主要语言
- TypeScript
- 星标
- 101k
- 派生
- 28.1k
- 平均合并
- 2 天 6 小时
- 30 天内合并 PR
- 307
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
angular/angular 的其他 Issue
-
area: docs
难度 2/5 1-3 小时 新手友好度 76/100
-
area: forms forms: signals
难度 2/5 1-3 小时 新手友好度 78/100
-
area: docs gemini-triaged
难度 2/5 1-3 小时 新手友好度 70/100
-
area: forms forms: signals
难度 2/5 1-3 小时 新手友好度 68/100
-
area: docs area: forms
难度 2/5 1-3 小时 新手友好度 76/100
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 70/100
-
难度 2/5 1-3 小时 新手友好度 75/100
mksglu/context-mode#1200 ·
-
难度 2/5 1-3 小时 新手友好度 75/100
jaegertracing/jaeger-ui#4506 ·
-
area:desktop area:ui bug platform:macos
难度 2/5 1-3 小时 新手友好度 75/100
anthropics/claude-code#96687 ·
-
good first issue
难度 1/5 1 小时以内 新手友好度 95/100
AOSSIE-Org/DebateAI#582 · 2 条评论 ·