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
This is necessary to exclude a race condition where the MutationObserver initialized by the instruction fired before the inputs are binded.
fixes#65678
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.
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
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.
* 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).
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
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
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.
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.
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.
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.
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.
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.
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<...>`
Currently we maintain the pathKeys internally, but do not expose them
through the `FieldContext`, this PR updates the `FieldContext` to expose
this property.
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.
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.
In some cases the logic order was not preserved properly when using `apply`. In particular this occurs when some logic is registered on a child of the root, followed by an apply to the root, followed by further logic registered on a child. In this case the final registered logic wound up running before the applied logic.
This happened because `FieldPathNode` for a child path was caching its `LogicNodeBuilder` at creation time. This meant that if the parent's `LogicNodeBuilder` changed (e.g., due to an `apply` call), the child would still be using the old one.
This commit fixes the issue by dynamically resolving the `LogicNodeBuilder` for a child path whenever it is accessed.
This commit changes arrays in a parent array to be tracked the same way
as primitive values like strings and numbers. This is necessary because
the tracking key symbol used to maintain identity for objects in an
array does not survive the array spread operation:
```
return {...oldValue} // tracking symbol preserved ✅
return [...oldValue] // tracking symbol lost ❌
```