Commit graph

693 commits

Author SHA1 Message Date
Miles Malerba
f254ff4f2e feat(forms): expose element on signal forms Field directive
This allows for easier focusing of the relevant element based on the
`field` property of a `ValidationError`

(cherry picked from commit aff8b248b3)
2025-12-09 09:47:56 -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
Anuj Chhajed
288238abef refactor(core): correct all typeof ngDevMode comparison patterns introduced by #63875
This change replaces all remaining occurrences of `typeof ngDevMode !== undefined`
with the correct `typeof ngDevMode !== 'undefined'` form. This aligns the codebase
with JavaScript typeof semantics and maintains consistency with other Angular code.

(cherry picked from commit 96b79fc393)
2025-12-08 10:30:06 -08:00
Miles Malerba
a4c436a058 refactor: followup cleanup for #65758 fallout
Fixes some issues left behind by recent refactor

(cherry picked from commit efde94a525)
2025-12-08 10:28:04 -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
Alex Rickabaugh
2c12661620 refactor(forms): simplify child field creation (#65802)
Previously, several values were being passed into the creation of
`FieldNodeStructure`s that were only used in the creation of child nodes.
Separately, we also passed a `createChildNode` function which these values
were passed back into.

Instead, this moves the small bit of logic from structure.ts behind the
`createChildNode` callback, which reduces the passing of values back-and-
forth and gives `createChildNode` a much more suitable signature.

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
609699ae17 perf(core): tree shake unused dynamic [field] binding instructions (#65599)
Move the instructions used to dynamically bind a `Field` directive to a
form control onto the `Field` itself. This way the instructions are only
retained if the app uses the `Field` directive.

PR Close #65599
2025-12-03 15:10:50 +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
Matthieu Riegler
8e0df4083d docs(docs-infra): Show examples on function overloads
(cherry picked from commit a784995a98)
2025-12-02 12:13:16 +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
cexbrayat
1a8dbd79e6 docs: FormArrayDirective mentions
(cherry picked from commit b0dd813664)
2025-11-14 16:40:44 +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
Leon Senft
33ed7d116b refactor(forms): do not infer accumulated metadata type from initial value
This removes the need to specify type arguments for
`reducedMetadataKey()` when the value returned from the `getIntial`
callback is a subtype of the accumulated type.

(cherry picked from commit 8866934334)
2025-11-11 12:00:12 -08:00
cexbrayat
eea81c2733 docs: remove error mentions in signal forms docs
`error` no longer exists and is called `validate`

(cherry picked from commit 800b01f5a1)
2025-11-11 08:30:13 -08:00
arturovt
8ab084754b refactor(core): mark VERSION as @__PURE__ for better tree-shaking
Annotate the `new Version(...)` call with `/* @__PURE__ */` to signal to optimizers that the constructor is side-effect free.

Without this hint, bundlers such as Terser or ESBuild may conservatively retain the `VERSION` instantiation even when unused. With the annotation, the constant can be tree-shaken away in production builds if not referenced, reducing bundle size.

(cherry picked from commit d3f67f6ca8)
2025-11-10 12:04:09 -08:00
SkyZeroZx
511200265b docs: improve discoverability of forms
(cherry picked from commit e3fc57e8fc)
2025-11-10 07:57:48 -08:00
Leon Senft
781a3299f9 perf(forms): only update interop controls when bound field changes
https://github.com/angular/angular/pull/64590 implemented change
detection for field bindings, but only for those bound to native or
custom form controls. This change extends that optimization to apply to
field bindings on interoperable controls built using Reactive Forms as well.

(cherry picked from commit 850f0d6b3d)
2025-11-07 11:58:20 -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
a3cb1dfd1a refactor(forms): nicer type errors
By intersecting with `object` instead of `unknown` in the primitive and
`FormControl` cases, we get TypeScript to show nicer type errors that
mention `FieldTree<...>` insetad of `() => FieldState<...>`

(cherry picked from commit 0efb3f6d1f)
2025-11-07 15:45:08 +00: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
Miles Malerba
8b4195fe7e refactor(forms): support dynamic object logic
extends `applyEach` to work on objects as well, conditionally applying
logic to each property of the object.

(cherry picked from commit c9058087ae)
2025-11-06 21:42:51 +00:00
arturovt
e90f9e418c refactor(forms): tree-shake ngControlStatusHost and ngGroupStatusHost
This commit removes `ngGroupStatusHost` variable because it's a side-effect, ending up preserving `ngControlStatusHost` and `ngGroupStatusHost`.

(cherry picked from commit 9f76fb61df)
2025-11-06 18:57:48 +00:00
kirjs
3bbcae6ce8 refactor(forms): add compatForm
This allows using reactive form controls in signal forms

(cherry picked from commit 60447945bc)
2025-11-06 18:51:31 +00:00
Alan Agius
3bed9f0f16
build: format md files
This commit configures prettier to format markdown files.
2025-11-06 10:07:13 -08:00
Miles Malerba
60ce31fb62 test(forms): re-enable some disabled tests
Adjusts test behavior to reflect current implementation and enables them

(cherry picked from commit fa02d96508)
2025-11-06 17:41:31 +00:00
Jessica Janiuk
a3c2fe819a Revert "refactor(common): Removes unused imports to clean up dependencies"
This reverts commit 6d3e0f1a51.
2025-11-06 09:05:05 -08:00
SkyZeroZx
6d3e0f1a51 refactor(common): Removes unused imports to clean up dependencies
Eliminates unnecessary imports to reduce clutter and improve maintainability

(cherry picked from commit ca3ef38143)
2025-11-06 16:35:32 +00:00
Miles Malerba
f3e2252f4b refactor(forms): add better support for Object.keys and Symbol.iterator
Improves support for using `Object.keys` (and related methods) and
`Symbol.iterator` to work with a `FieldTree` of unknown shape.

(cherry picked from commit e87f423d90)
2025-11-06 15:51:31 +00:00
Miles Malerba
d1f4c8c18c refactor(forms): improved select support
When we set the `value` on a `<select>` element we're really just
setting the selected `<option>`. It treats the selected option as the
source of truth, rather than the actual value. This means that if
options are added or removed or their value changes, the `<select>` may
wind up with a different `value` than what's in our model.

This PR resolves this issue by adding a `MutationObserver` to the select
that is used to resync its value to the model whenever the options may
have changed.

(cherry picked from commit 194b41199b)
2025-11-06 15:49:09 +00:00
Miles Malerba
c3b4bea951 refactor(forms): improve discoverability of ValidationError flavors (#64747)
Improves discoverability by putting the WithField, WithoutField, etc as
subtypes of the main ValidationError type

PR Close #64747
2025-11-05 22:42:59 +00:00
Leon Senft
507b3466ee perf(forms): implement change detection for field control bindings
For each field state property, check if it has changed since the last
time it was checked before writing it the corresponding form control
property.

The `pattern` and `required` properties of the field state now return a
default value rather than `undefined` if not defined by metadata.

(cherry picked from commit 41be02da2f)
2025-11-04 16:11:33 -08:00
SkyZeroZx
d3ed745653 refactor(forms): remove redundant providedIn: 'root' from injection tokens
Removes unnecessary `providedIn: 'root'` declarations from injection tokens

(cherry picked from commit 78e6716f40)
2025-11-03 16:31:58 -08:00
Kristiyan Kostadinov
a4fe078969 refactor(common): remove unused import (#64699)
Cleans up unused code to improve readability and maintainability.

PR Close #64699
2025-10-30 19:27:34 +00:00