Commit graph

692 commits

Author SHA1 Message Date
Miles Malerba
aff8b248b3
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`
2025-12-09 09:47:51 -08:00
Miles Malerba
ebc5c2b083 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
2025-12-09 09:21:41 -08:00
cexbrayat
9fe9566581 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
2025-12-08 10:30:43 -08:00
Anuj Chhajed
96b79fc393 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.
2025-12-08 10:30:01 -08:00
Miles Malerba
efde94a525 refactor: followup cleanup for #65758 fallout
Fixes some issues left behind by recent refactor
2025-12-08 10:27:59 -08:00
Kirill Cherkashin
5be33048cc
refactor(forms): Break logic.ts into separate files
This would make it easier to navigate
2025-12-04 11:33:20 -08:00
Alex Rickabaugh
b96f65a963 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:42 -08:00
Alex Rickabaugh
37472e9185 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:42 -08:00
SkyZeroZx
7d1e502345 feat(forms): Allows transforms on FormUiControl signals
Extends the `FormUiControl` interface to allow `InputSignalWithTransform` in addition to `InputSignal` for its properties.

Fixes #65756
2025-12-03 15:13:01 +01:00
Leon Senft
e6d5632a30 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:49 +01:00
Leon Senft
cd7ae7e2ce 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
f35b2ef47c 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
2025-12-02 12:59:49 +01:00
Matthieu Riegler
a784995a98 docs(docs-infra): Show examples on function overloads 2025-12-02 12:13:11 +01:00
kirjs
dec222d4d7 refactor(forms): Make reset take value
Now you can do form.reset({name: 'cat', age: 4});
2025-11-25 10:51:35 -05:00
Miles Malerba
c70e246c23
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.
2025-11-25 10:33:38 -05:00
Leon Senft
3a1eb07c46 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.
2025-11-25 09:15:58 -05:00
Leon Senft
f97a1d4856 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
2025-11-24 13:48:17 -05:00
Miles Malerba
7ddf4a6b07 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
2025-11-19 14:27:52 -08:00
Matthieu Riegler
b41a94bc85 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
2025-11-17 09:41:54 -08:00
Leon Senft
c727df5d38
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.
2025-11-17 09:40:58 -08:00
Leon Senft
b1037ec2f0 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).
2025-11-17 08:37:26 -08:00
Miles Malerba
722292f215 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
4f8ab4fa3c 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
fc1ef79ad4 build(forms): expose signal forms compat package
Hooks up @angular/forms/signals/compat to be released and have its docs
published
2025-11-14 09:23:36 -08:00
cexbrayat
b0dd813664 docs: FormArrayDirective mentions 2025-11-14 08:40:41 -08:00
Leon Senft
acb78eeb7a 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.
2025-11-14 08:37:42 -08:00
Leon Senft
98ce9a7b17 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.
2025-11-13 09:59:19 -08:00
Leon Senft
d337cfb68f 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.
2025-11-11 12:00:09 -08:00
Leon Senft
8866934334 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.
2025-11-11 12:00:09 -08:00
cexbrayat
800b01f5a1 docs: remove error mentions in signal forms docs
`error` no longer exists and is called `validate`
2025-11-11 08:30:08 -08:00
arturovt
d3f67f6ca8 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.
2025-11-10 12:04:04 -08:00
SkyZeroZx
e3fc57e8fc docs: improve discoverability of forms 2025-11-10 07:57:43 -08:00
Leon Senft
850f0d6b3d 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.
2025-11-07 11:58:17 -08:00
Kristiyan Kostadinov
10915f2f5f 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.
2025-11-07 11:57:50 -08:00
Miles Malerba
0efb3f6d1f 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<...>`
2025-11-07 07:45:05 -08:00
Miles Malerba
faccf03ee0
refactor(forms): allow FieldTree of recursive type
Adjusts our typings to work better with recursive types and avoid
infinite recursion in the type system.
2025-11-06 13:43:43 -08:00
Miles Malerba
2fd8dc9195
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.
2025-11-06 13:43:13 -08:00
Miles Malerba
c9058087ae
refactor(forms): support dynamic object logic
extends `applyEach` to work on objects as well, conditionally applying
logic to each property of the object.
2025-11-06 13:42:48 -08:00
arturovt
9f76fb61df refactor(forms): tree-shake ngControlStatusHost and ngGroupStatusHost
This commit removes `ngGroupStatusHost` variable because it's a side-effect, ending up preserving `ngControlStatusHost` and `ngGroupStatusHost`.
2025-11-06 10:57:45 -08:00
kirjs
60447945bc refactor(forms): add compatForm
This allows using reactive form controls in signal forms
2025-11-06 10:51:28 -08:00
Alan Agius
26fed34e0e
build: format md files
This commit configures prettier to format markdown files.
2025-11-06 10:03:05 -08:00
Miles Malerba
fa02d96508
test(forms): re-enable some disabled tests
Adjusts test behavior to reflect current implementation and enables them
2025-11-06 09:41:28 -08:00
SkyZeroZx
ca3ef38143 refactor(common): Removes unused imports to clean up dependencies
Eliminates unnecessary imports to reduce clutter and improve maintainability
2025-11-06 08:35:28 -08:00
Miles Malerba
e87f423d90 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.
2025-11-06 07:51:28 -08:00
Miles Malerba
194b41199b 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.
2025-11-06 07:49:06 -08:00
Miles Malerba
96388b6d8a 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
41be02da2f 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.
2025-11-05 00:11:22 +00:00
SkyZeroZx
78e6716f40 refactor(forms): remove redundant providedIn: 'root' from injection tokens
Removes unnecessary `providedIn: 'root'` declarations from injection tokens
2025-11-04 00:31:52 +00:00
Kristiyan Kostadinov
b9e2ccdda8 refactor(common): remove unused import (#64699)
Cleans up unused code to improve readability and maintainability.

PR Close #64699
2025-10-30 19:27:33 +00:00
Miles Malerba
662f0e5c00
feat(docs-infra): add support for namespaces
Adds support for generating docs for namespaces (and merged declarations
of namespace + type)
2025-10-29 20:22:21 +00:00