97 Commits

Author SHA1 Message Date
Matthieu Riegler 82d556a8fb fix(forms): Ensure the control instruction comes after the other bindings
Prior to this change, binding to radio value was sensitive to the order in which `value` & `formField` where binding in the template.
The compiler change makes that order non-important.

fixes #66402

(cherry picked from commit 65fa5b5439)
2026-01-12 21:49:23 +00:00
cexbrayat 5cbb66fdf2 test(forms): improve test coverage for multiple pattern validators
Add comprehensive test cases to validate behavior when multiple
pattern validators are applied to the same field.

These tests clarify that multiple patterns operate with AND logic,
where each pattern is validated independently and produces its own
error when it doesn't match.

(cherry picked from commit 7fd076bdd3)
2026-01-12 21:48:19 +00:00
Miles Malerba 0055f3cc79 fix(forms): Rename signal form [field] to [formField]
This completes the rename started in #66136. `[field]` is too generic of
a selector for the forms system to own, and likely to cause naming
collisions with existing components. Therefore it is being renamed to
`[formField]`

(cherry picked from commit 5671f2cc07)
2026-01-09 22:33:13 +00:00
Leon Senft ff531752b0 refactor(forms): remove customError()
Remove the `customError` function and `CustomValidationError` type.

These were made obsolete by support for returning plain object literals
as custom errors.

This also catches few `field` properties that were missed in the
renaming to `fieldTree`.

(cherry picked from commit 46dbd18566)
2026-01-07 15:07:35 -05:00
SkyZeroZx ae1dc16bb0 fix(forms): clean up abort listener after timeout
Removes the abort event listener once the debounce timeout completes.

This avoids lingering listeners, prevents potential memory leaks, and ensures
the abort logic runs at most once.

(cherry picked from commit e7d99f02cb)
2026-01-07 14:07:28 -05:00
Miles Malerba e3fba182f9 feat(forms): add [formField] directive
This will replace the `[field]` directive, since `[field]` is a very
generic name for signal forms to commandeer

refactor(forms): hook up `formField` directive in compiler

Hooks up the `formField` direcive to get the same treatment as the
`field` directive in the compiler.

apply updated formatting

(cherry picked from commit 2d85ae5811)
2026-01-06 17:21:09 -05:00
Leon Senft af1d4acb9d refactor(forms): bind native properties on interop controls
The framework will now bind all field state properties to their
corresponding native properties (if any) on interop form controls (those
using `ControlValueAccessor`), excluding those handled explicitly by
`ControlValueAccessor` such as `disabled`.

(cherry picked from commit 82a2de201f)
2026-01-06 13:12:21 -05:00
Leon Senft 0e8b9d8cde refactor(forms): bind field properties to all directives on interop controls
The framework will now bind the field state properties to all matching
directive inputs on form controls using Reactive Forms'
`ControlValueAccessor`.

(cherry picked from commit 15bddbdcda)
2026-01-06 13:12:21 -05:00
Leon Senft ff37a13913 refactor(forms): bind field properties to all directives on custom controls
The framework will now bind the field state properties to all matching
directive inputs on custom form controls.

(cherry picked from commit 0c8f15d546)
2026-01-06 13:12:21 -05:00
Leon Senft a4ba820d36 refactor(forms): bind field properties to all directives on native controls
Since the `Field` directive manages binding `FieldState` properties to
the underlying form control automatically, the type checker prohibits
explicit bindings to the same properties to avoid conflicts. This proved
problematic in cases where developers wanted to bind these properties to
the inputs of other directives on form controls. Now the framework will
bind the field state properties to all matching directive inputs on
native controls.

Fix #65617

(cherry picked from commit c149f47ef6)
2026-01-06 13:12:21 -05:00
kirjs 6bd22df987 fix(forms): Support readonly arrays in signal forms
This would allow using `readonly Array<...>` in types

(cherry picked from commit 282220d032)
2026-01-02 08:26:09 +01:00
Leon Senft 85621f11e1 test(forms): add test coverage for binding errors to custon controls
This input was missing dedicated test coverage.

(cherry picked from commit e7745dc9dd)
2026-01-02 08:09:39 +01:00
Leon Senft 6d167bd64c test(forms): remove duplicate test case
We had two test cases that tested the `Field` directive synchronizes
with a custom checkbox control components.

(cherry picked from commit 8832fc01b2)
2026-01-02 08:09:39 +01:00
Leon Senft ec110f170b fix(forms): allow custom controls to require pending input
* Allow custom controls to make `pending` a required input
* Refactor test for `pending` input to be consistent with other control
  properties
* Test that `pending` inputs are reset when the field binding changes

(cherry picked from commit 1a4c3eb1d0)
2026-01-02 08:09:39 +01:00
Leon Senft 79d16919a5 test(forms): remove obsolete test case
There's no longer a need to test that inputs are set before
initialization on custom controls now that required inputs are
supported.

(cherry picked from commit 9ad603fa11)
2026-01-02 08:09:39 +01:00
Leon Senft 8c83266816 test(forms): refactor and improve test coverage for disabledReasons input
* Refactor test for `disabledReasons` input to be consistent with other control
  properties
* Test that `disabledReasons` inputs are reset when the field binding changes

(cherry picked from commit 4f73a350a5)
2026-01-02 08:09:39 +01:00
Leon Senft 561772b152 fix(forms): allow custom controls to require dirty input
* Allow custom controls to make `dirty` a required input
* Refactor test for `dirty` input to be consistent with other control
  properties
* Test that `dirty` inputs are reset when the field binding changes

(cherry picked from commit 89c37f1f7f)
2026-01-02 08:09:39 +01:00
Leon Senft 929306e658 test(forms): refactor and improve test coverage for invalid input
* Refactor test for `invalid` input to be consistent with other control
  properties
* Test that `invalid` inputs are reset when the field binding changes

(cherry picked from commit b563b5cfc2)
2026-01-02 08:09:39 +01:00
Leon Senft f0fb1d8581 fix(forms): allow custom controls to require hidden input
* Allow custom controls to make `hidden` a required input
* Refactor test for `hidden` input to be consistent with other control
  properties
* Test that `hidden` inputs are reset when the field binding changes

(cherry picked from commit 82edf18427)
2026-01-02 08:09:38 +01:00
Leon Senft 9748b0d5da fix(forms): support custom controls with non signal-based models
* Recognize directives with non signal-based models as valid custom controls
* Relax type checker to allow non signal-based models

The `FormValueControl` and `FormCheckboxControl` interfaces still
require a `model()`-input, however, a custom control need not implement
either interface to be bound by the `Field` directive.

All of the following examples can be used to define a custom control:

```ts
// Preferred: model()
class MyFormControl implements FormValueControl<string> {
  readonly value: model.required<string>();
}

// Supported: input() + output()
class MyFormControl {
  readonly value: input.required<string>();
  readonly valueChange: output<string>();
}

// Supported: @Input() + @Output()
class MyFormControl {
  @Input({required: true}) value!: string;
  @Output() valueChange: new EventEmitter<string>();
}
```

The latter two may still choose to implement `FormUiControl` for other
properties, but again it is not required.

Fix #65478

(cherry picked from commit cb09fb8308)
2026-01-02 08:09:07 +01:00
Matthieu Riegler feed89e83c ci: reformat files
This is after we've slightly changed a rule in #66056

(cherry picked from commit 6270bba056)
2025-12-16 14:44:25 -08:00
Miles Malerba 729b96476b refactor(forms): rename field to fieldTree in FieldContext and ValidationError
BREAKING CHANGE:

(cherry picked from commit ae0c59028a)
2025-12-16 10:26:26 -08:00
kirjs 45e967b295 refactor(forms): convert Signal Forms errors to use RuntimeError
- Added 13 new error codes to forms/src/errors.ts (1900-1999)
- use RuntimeError

(cherry picked from commit 3a01d72850)
2025-12-15 11:44:06 -08:00
Miles Malerba 81772b420d feat(forms): pass field directive to class config
Updates signal forms to pass the full `Field` directive to the class
configuration functions, rather than just the state. This allows
developers to take the element as well as the state into consideration
when deciding classes to apply.

Closes #65762

BREAKING CHANGE: The shape of `SignalFormsConfig.classes` has changed

Previously each function in the `classes` map took a `FieldState`. Now
it takes a `Field` directive.

For example if you previously had:
```
provideSignalFormsConfig({
  classes: {
    'my-valid': (state) => state.valid()
  }
})
```

You would need to update to:
```
provideSignalFormsConfig({
  classes: {
    'my-valid': ({state}) => state().valid()
  }
})
```

(cherry picked from commit 348f149e8b)
2025-12-12 08:07:57 -08:00
Matthieu Riegler cbb10179c8 fix(forms): allow resetting with empty string
Both empty string and null values should be accepted when resetting

fixes #65949

(cherry picked from commit 14713d0992)
2025-12-10 10:04:58 -08:00
kirjs 6d7475582f fix(forms): Reuse key in parent in compat structure
This fixes the "Compat nodes do not use keyInParent." error when trying to bind compatField.

(cherry picked from commit 179b4cba67)
2025-12-09 12:59:27 -08:00
Miles Malerba 5880fbc73c feat(forms): redo the signal forms metadata API
This PR makes a number of changes to the metadata API to address design
flaws in the previous API. Some of the changes include:

- Replaces the previous `MetadataKey` and `AggregateMetadataKey` with a
  single unified `MetadataKey` that is used for all metadata.
- The new `MetadataKey` is only defined for fields that explicitly set
  it in their schema logic
- All metadata now has reducer / aggregate behavior
- The new `MetadataKey` has an option to create a managed key which
  wraps the result of its computed aggregate into some other structure
  such as a `Resource` or `linkedSignal`
- There are now two APIs to create metadata keys
  - `createMetadataKey` for pure computed metadata
  - `createManagedMetadataKey` for metadata that manages its computation
    internally

(cherry picked from commit ebc5c2b083)
2025-12-09 09:21:45 -08:00
cexbrayat 55fc677cef fix(forms): add signals for dirty, hidden, and pending states in custom controls
FormUiControl states that hidden, pending and dirty will be bind in custom controls, but this is currently not the case.

Fixes #65575

(cherry picked from commit 9fe9566581)
2025-12-08 10:30:48 -08:00
Kirill Cherkashin 7de8558219 refactor(forms): Break logic.ts into separate files
This would make it easier to navigate

(cherry picked from commit 5be33048cc)
2025-12-04 11:33:26 -08:00
Alex Rickabaugh bf1c12cd93 fix(forms): memoize reads of child fields in signal forms (#65802)
Previously, navigating a `FieldTree` in signal forms involved reactive reads
of the value of the parent field(s), both directly and via `.childrenMap()`.

This meant that on _any_ change to the value of a field, reactive
notifications would trigger updates of computeds, reruns of effects, etc.
So for example, this effect would run on every change to the form:

```ts
const f = form(signal({data: 'abc', unrelated: 0}));
effect(() => {
  // accessing f.data incurs a dependency on f().value() which changes
  // on every change in the whole form
  console.log(f.data().value());
});
```

This is deeply counterintuitive and troublesome when attempting to write
effect logic, and also results in `computed`s unnecessarily updating.

This change introduces the concept of a "reader" computed, which memoizes
the access of a field at a given key via the reactive graph. With this, the
same `f.data` access above now depends on the `data` reader in `f` only,
which is effectively a constant computed. As a result, the effect only
reruns on changes to `data`'s value, as intended.

PR Close #65802
2025-12-03 12:52:43 -08:00
SkyZeroZx 6b4ab876e8 feat(forms): Allows transforms on FormUiControl signals
Extends the `FormUiControl` interface to allow `InputSignalWithTransform` in addition to `InputSignal` for its properties.

Fixes #65756

(cherry picked from commit 7d1e502345)
2025-12-03 15:13:06 +01:00
Leon Senft a5dbd4b382 fix(forms): support dynamic [field] bindings (#65599)
Support binding a `Field` directive to a component created dynamically
with `createComponent()`.

Fix #64632

PR Close #65599
2025-12-03 15:10:49 +01:00
Matthieu Riegler 017672f742 refactor(compiler): Generate the controlCreate instruction after the native element has been created
This is necessary to exclude a race condition where the MutationObserver initialized by the instruction fired before the inputs are binded.

fixes #65678

(cherry picked from commit f35b2ef47c)
2025-12-02 12:59:53 +01:00
kirjs e3f5f34732 refactor(forms): Make reset take value
Now you can do form.reset({name: 'cat', age: 4});

(cherry picked from commit dec222d4d7)
2025-11-25 10:51:38 -05:00
Miles Malerba 7d5c7cf99a feat(forms): add DI option for classes on Field directive
Adds a DI configuration option for signal forms that allows the
developer to specify CSS classes that should be automatically added
by the `Field` directive based on the field's status.

(cherry picked from commit c70e246c23)
2025-11-25 10:33:41 -05:00
Leon Senft 8acf5d2756 fix(forms): allow dynamic type bindings on signal form controls
The type checker will no longer prohibit binding the Signal Forms `[field]`
directive to an input with a dynamic `[attr.type]` or `[type]` binding.

(cherry picked from commit 3a1eb07c46)
2025-11-25 09:16:01 -05:00
Leon Senft 4845a33018 refactor(forms): support custom control directives
Support binding `[field]` to directives that implement
`FormValueControl` or `FormCheckboxControl`.

The `[field]` binds to whichever directive (or component) matches first in the
event there are multiple implementations. We are considering whether to make
this an error state, which could be reported during type checking.

Closes #63910, Closes #64992

(cherry picked from commit f97a1d4856)
2025-11-24 13:48:20 -05:00
Miles Malerba de5fca94c5 fix(forms): run reset as untracked
Run the signal forms `reset()` as untracked so it does not trigger
`effect` to rerun when the model changes

Fixes https://github.com/angular/angular/issues/65322

(cherry picked from commit 7ddf4a6b07)
2025-11-19 22:27:55 +00:00
Matthieu Riegler 91d8d55a80 fix(forms): Set error message of a schema error.
Use the error message of the issue as the error message of the error itself.

fixes #65247

(cherry picked from commit b41a94bc85)
2025-11-17 17:41:57 +00:00
Leon Senft e682f00e29 refactor(forms): reduce boilerplate needed to define custom controls
An early piece of feedback received regarding custom controls hosted on
native inputs was that they required a lot of boilerplate to bind
`FieldState` properties. Each property required an input to accept the
property, and a host binding to forward it to the native control.

(cherry picked from commit c727df5d38)
2025-11-17 17:41:02 +00:00
Leon Senft d89e522a1f fix(forms): debounce updates from interop controls
* Apply any debounce rules to updates from interop controls (if configured).
* Add tests to ensure debouncing works for all control types (native, custom,
  and interop).

(cherry picked from commit b1037ec2f0)
2025-11-17 16:37:29 +00:00
Miles Malerba e1a7c35370 refactor(forms): improve typing on min & max (#65212)
If we're calling `min` on a path that's guaranteed to be `number` we
don't want to make the users validator function handle the `null` or
`string` cases.

This uncovered an issue in the `SchemaTreePath` type which needed to be
fixed by preventing the model type from being distributed over.

PR Close #65212
2025-11-14 21:56:58 +00:00
Miles Malerba ff4633dab8 refactor(forms): allow passing number|string|null paths to min & max (#65212)
Relaxes the constraints on which paths can be used with the `min` &
`max` validation rules, since people may want to validate a
potentially-null number, or a numeric value represented as a string

PR Close #65212
2025-11-14 21:56:58 +00:00
Miles Malerba 966bfae337 build(forms): expose signal forms compat package
Hooks up @angular/forms/signals/compat to be released and have its docs
published

(cherry picked from commit fc1ef79ad4)
2025-11-14 17:23:39 +00:00
Leon Senft 8e988124ab test(forms): [field] inputs on components should just pass through
Test that a component with a bound `[field]` input is not treated as a
control, and that `fieldBinding` does not include the corresponding
`Field` instance.

(cherry picked from commit acb78eeb7a)
2025-11-14 16:37:45 +00:00
Leon Senft 37463525be refactor(forms): use AbortSignal to cancel debounced updates
Add an `AbortSignal` parameter to `Debouncer`. Implementations may
choose to accept this parameter to be informed when a debounced
operation is aborted. This may be useful for canceling pending timers or
avoiding unnecessary work.

(cherry picked from commit 98ce9a7b17)
2025-11-13 17:59:22 +00:00
Leon Senft a278ee358c feat(forms): add debounce() rule for signal forms
The `debounce()` rule allows developers to control when changes to a
form control are synchronized to the form model.

This feature necessitated some changes to `FieldState`:

  * `controlValue` is a new signal property that represents the current
    value of a form field as it appears in its corresponding control.

  * `value` conceptually remains unchanged; however, its value may lag
    behind that of `controlValue` if a `debounce()` rule is applied.

The `debounce()` rule essentially manages when changes to `controlValue` are
synchronized to `value`. The intent is that an expensive or slow
validation rule can react to the debounced `value`, rather than a more
frequently changing `controlValue`.

Directly updating `value` immediately updates `controlValue`, and cancels any
pending debounced updates.

When multiple `debounce()` rules are applied to the same field, the last
currently active rule is used to debounce an update. These rules are
applied to child fields as well, unless they override them with their
own rule.

(cherry picked from commit d337cfb68f)
2025-11-11 12:00:13 -08:00
Kristiyan Kostadinov cc25f96f02 test(forms): fix type checking errors in signal form tests
Fixes some type checking errors in the signal forms tests that accumulated while there wasn't any type checking.

(cherry picked from commit 10915f2f5f)
2025-11-07 11:57:53 -08:00
Miles Malerba 6d6fda8610 refactor(forms): allow FieldTree of recursive type
Adjusts our typings to work better with recursive types and avoid
infinite recursion in the type system.

(cherry picked from commit faccf03ee0)
2025-11-06 21:43:46 +00:00
Miles Malerba 9ea1c2216d refactor(forms): expose pathKeys as part of the API
Currently we maintain the pathKeys internally, but do not expose them
through the `FieldContext`, this PR updates the `FieldContext` to expose
this property.

(cherry picked from commit 2fd8dc9195)
2025-11-06 21:43:15 +00:00