Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

Signal Forms should not enforce native `min` / `max` / `minLength` / `maxLength` types on custom controls

未关闭
#70,600 1 条评论 5 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 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 内容生成。

描述

area: forms forms: signals
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:

https://github.com/angular/angular/blob/9a58353b1b680f162a55969965ae6a90ae20316d/packages/forms/signals/src/api/control.ts#L86-L109

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

https://stackblitz.com/edit/ng-signal-forms-custom-control-min-max-props-bug?file=src%2Fapp%2Fapp.ts,src%2Fapp%2Fapp.html

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

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

angular/angular 的其他 Issue

查看 angular/angular 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。