Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

オープン
#70,600 コメント 1 件 リアクション 5 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
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時間
マージ済み PR(30日)
307

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

angular/angular のほかの issue

angular/angular の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。