This adds support for a `debounce` option to the `validateAsync` and `validateHttp` functions.
This allows developers to debounce the triggering of async validators to improve performance.
A `DebounceTimer` type was also added to `@angular/core` to represent the wait condition parameters uniformly.
Added a `getError(kind: string)` method to `FieldState` that returns the first validation error of a given kind, or `undefined` if no such error exists. This method is reactive and will re-evaluate when errors change.
Fixes#63905
Also updated public API goldens and added unit tests.
The `[formRoot]` directive will no longer call `submit()` if the bound
form doesn't define its own submission options. This allows the
directive to be used solely for the default behavior it provides:
setting `novalidate` on the `<form>` and calling `preventDefault()` on
the `submit` event.
Fix#67367
PR Close#67727
Currently, Signal Forms eagerly instantiates all nodes in the form tree because `childrenMap` iterates over the `value` and creates a `FieldNode` for every property. This ensures validation side-effects are run early, but creates pure overhead for fields without validation logic unless explicitly accessed.
This commit makes `childrenMap` lazy by default, skipping materialization for children without schema logic. This is achieved by introducing `hasLogicRules()` and `anyChildHasLogic()` across the `LogicNode` hierarchy. Fields are now only instantiated when a direct read occurs via `getChild()` (which calls the new `ensureChildrenMap()`) or if their subtree requires eager evaluation due to existing validation rules.
Fixes#67212
This commit introduces a formal mechanism to manually re-trigger
asynchronous validations in Signal Forms, addressing #66994.
It exposes a `reloadValidation` method on the `FieldState` interface
that recursively cascades down the form tree and invokes the underlying
`ResourceRef`'s `reload()` method for any metadata keys tagged with the
internal `IS_ASYNC_VALIDATION_RESOURCE` symbol.
Fixes#66994
This commit resolves an issue where using an uninstantiated generic type
parameter in a signal form model caused TypeScript compilation failures due to
distributive conditional types (#66596). The previous attempt to fix this issue
by tuple-wrapping everything caused another bug (#65535) that prevented property
access on generic unions.
This commit balances the need to resolve nested generic property access while
handling infinitely recursive generic structures without depth errors.
What changed and why:
- Base State Wrappers: Tuple wrappers (`[TModel] extends [AbstractControl]`) are
applied to `FieldTreeBase` to safely defer generic evaluation. This prevents
primitive unions (like `boolean`) from incorrectly evaluating to `never`.
- Naked Map Over Children: Object subfield checks (`TModel extends Record`) are
re-evaluated as purely naked conditionals. Eager distribution over generics
allows users to directly access shared properties of unresolved union types.
- Array Interface Deflection: `ReadonlyArrayLike<T>` generic abstraction is
redefined as an explicit `interface` instead of a mapped `Pick` type alias.
This optimally intercepts TypeScript from eagerly evaluating infinitely
recursive array structures (e.g. `RecursiveType = (number | RecursiveType)[]`).
- Overloaded Context Methods: `FieldNodeContext.stateOf` and `fieldTreeOf` are
defined as explicitly overloaded class methods and lexically bound (`this`) in
the constructor. These changes are required to safely align the runtime bindings
with the tautological conditionals implemented in the `RootFieldContext`
interface structure.
Fixes#65535
`markAsTouched()` now marks all descendants as touched. In general this
method is called when controls update the model. Most controls update
leaf nodes, in which case this change has no effect.
Marking all descendants allows triggering validation for subsections of
a form, independently from having to call `submit()` on the entire form.
`markAsTouched()` now accepts a `MarkAsTouchedOptions` parameter, which
includes a `skipDescendants` property. This can be used mark only the
receiving field as touched: `node.markAsTouched({skipDescendants: true})`.
Reactive logic in forms is not intended to mutate state, but this was
poorly communicated by the permissive and highly mutable field context
provided to all logic functions. This change splits all of the
state-related API into writable and readonly interfaces.
* Top-level functions that produce a `FieldTree` (e.g. `form()`) expose
writable signals (e.g. `value: WritableSignal<T>`) and mutating
methods (e.g. `markAsDirty()`).
* Reactive logic expose readonly signals (e.g. `value: Signal<T>`) and
omit mutating methods.
Adds a test verifying that `transformedValue` exposes parse errors via
the returned signal's `parseErrors()` property when no FormField
context is present.
This ensures that:
- parse errors are still observable without DI-based field propagation
- the model is not updated when `parse` omits `value`
- valid input clears parse errors and updates the model
This test protects the documented contract that DI-based error
propagation is expected for FormValueControl usage, while standalone
usage relies on explicit consumption of `parseErrors()`.
Expands the `debounce` rule configuration to accept `'blur'`. When this option
is provided, the rule will delay model synchronization until the field loses
focus (is touched). This introduces a debouncer that defers resolution
until the framework automatically aborts pending debounces upon touch events.
Aligns the errors returned from the `parse` function in
`transformedValue` to use the same convention as the rest of signal
forms (a property called `error` that can contain a single error or list
of errors)
Changes `parsedErrors` to a `linkedSignal` based on the model value.
This ensures that the parse errors are reset if the model changes from
outside the control.
PR Close#66917
The `action` and `onInvalid` handlers now recevie two pieces of
information:
1. The form that is being submitted
2. The specific field that the submit was triggered on
Remove the `submit()` method on field state - supporting this is complex
from a typing perspective, since the `FieldState` only knows its
`TValue` type, not the `TModel` type of its owning `FieldTree`. Rather
than try to pack additional generics on to `FieldState`, we'll just
leave the `submit` function as a standalone importable function.
The `fieldTree` property of `FieldState` returns its associated
`FieldTree`.
Note that the round trip from `FieldTree` to `FieldState` and back will
lose type information. This is because `FieldState` intentionally does
not know whether it came from a pure Signal Forms field tree, or a
Reactive Forms compatible field tree:
```ts
// Pure Signal Forms:
const x: FieldTree<string>;
x(); // FieldState<string>;
x().fieldTree; // FieldTree<unknown>
// Reactive Forms compatibility:
const y: FieldTree<FormControl<string>>;
y(); // FieldState<string>;
y().fieldTree; // FieldTree<unknown>;
```
Reoves the `parseErrors` property on `FormUiControl` and instead
introduces a new utility `transformedValue` that automatically handles
synchronizing the raw value and model value using the given `parse` and
`format` functions. It also automates the reporting of `parseErrors` to
the `FormField`, simplifying the API surface
Allow `validateStandardSchema()` to consume a computed schema so
validation rules stay in sync when the schema changes over time.
This supports schemas stored in computed signals (e.g. zod schemas that
depend on input signals) and ensures the effective schema updates after
initialization instead of being captured once.
Fixes#66867
Updates FormOptions to accept a submission configuration object.
This allows defining default submit options (action, validation behavior, etc.)
when creating the form, which can be overridden when calling submit().
Changes the `submit` function signature to accept a `FormSubmitOptions` object instead of a direct action callback.
This allows for more flexibility, including:
- `action`: The standard submit action to perform with the data.
- `onInvalid`: A callback to execute when the submit action is not triggered due to failing validation
- `ignoreValidators`: Controls whether pending validators or invalid validators should be ignored
Also updates the return value of `submit` to a `Promise<boolean` to indicate submission success.
Remove `setControlValue()` from `FieldState` and convert `controlValue` to a
`WritableSignal` whose setter implements the debounced syncing behavior
of `setControlValue()`.
Don't touch hidden, disabled, or readonly fields on submit, since they
don't contribute to form validity. This also prevents errors from
appearing immediately if they're later made interactive.
Fix#66344
Ensure `submit()` behaves as expected while a form is pending.
- Submission is not blocked by pending validation.
- Submission errors prevent pending validation errors from appearing
after they resolve on the same field.
- Submission errors don't prevent pending validation errors from
appearing after they resolve on subfields.
Parse errors allow a custom control to communicate that it is currently
unable to produce a valid value.
Parse errors are reported by implementing the optional `parseErrors`
property on the `FormUiControl`. The property should be a signal of the
current parse errors.
Also renames several `*Field` types to `*FieldTree`. This aligns with the new naming of the concept after `Field` was renamed
to `FieldTree`.
Previously we were unconditionally setting the control value back into
the value, regardless of if it had actually been changed. This PR
changes the logic to flush the pending sync on touch if there is one, or
just skip it if there isn't.
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.
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]`
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`.
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.