Commit graph

872 commits

Author SHA1 Message Date
arturovt
f43bad4d7b perf(forms): avoid spurious recomputation in FormField.parseErrors
`parseErrors` in `FormField` always produced a new array on every recomputation, even when nothing actually changed. The `?? []` fallback created a new empty array whenever `parseErrorsSource` was undefined, and `.map()` also returned new object references each time.

Since computed signals use reference equality by default, those new arrays were treated as changed values. That caused unnecessary updates to propagate through `validationState.parseErrors` and the combined errors chain, triggering extra recomputations during change detection.

Fix this by adding `{equal: shallowArrayEquals}` to the `parseErrors` computed, matching the existing `errors` computed and the validation computeds in `field/validation.ts`.

This prevents empty arrays from triggering updates while still correctly propagating real parse-error changes.
2026-05-18 13:08:33 -07:00
Douglas Parker
1963a0eb18 refactor(forms): add provideExperimentalWebMcpForms
This enables the use of the `experimentalWebMcpTool` option on signal forms and implicitly declares a WebMCP tool based on the form data model. This is an experiment inspirted by the WebMCP declarative forms API to see if Angular's framework-level knowledge of the form's declarative data model can produce higher quality WebMCP tools than the web standard can on its own with less effort from the developer.

Example:

```typescript
// main.ts

import {bootstrapApplication} from '@angular/platform-browser';
import {provideExperimentalWebMcpForms} from '@angular/forms';
import {MyComp} from './form';

bootstrapApplication(MyComp, {
  providers: [
    // Activate the feature.
    provideExperimentalWebMcpForms(),
  ],
});
```

```typescript
// form.ts

import {Component, signal} from '@angular/core';
import {form} from '@angular/forms';

@Component({ /* ... */ })
export class MyComp {
  private readonly f = form(signal({
    firstName: '',
    lastName: '',
  }), {
    // Implicitly creates a WebMCP tool named `createUser` which accepts a `firstName` and `lastName` as parameters.
    experimentalWebMcpTool: {
      name: 'createUser',
      description: 'Creates a user with the given name.',
    },

    // Invokes the submit action when the agent calls the WebMCP tool.
    submission: {
      action: () => {
        console.log('User clicked submit, or agent called the tool!');
      },
    },
  });

  // ...
}
```
2026-05-15 11:35:22 -07:00
Matthieu Riegler
a7dab601fa refactor(core): use the @Service decorator where possible.
A few bytes to win.
Added only on the services that don't rely on constructor DI.
2026-05-07 17:03:30 -06:00
kirjs
043055f6de refactor(forms): support when consistently for maxDate and minDate validators
This commit updates maxDate and minDate to consistently check and apply the 'when' option for conditional validation.
2026-05-06 14:10:12 -07:00
kirjs
0806b2f02b refactor(forms): use overloads and JSDoc for deprecations
This commit removes runtime console warnings and uses TypeScript overloads with JSDoc @deprecated annotations to handle backward compatibility for conditional rules.
2026-05-06 14:10:12 -07:00
kirjs
7d9862f0be refactor(forms): support deprecated signatures for backward compatibility
This commit restores support for passing functions directly to hidden, disabled, and readonly rules, marking them as deprecated.
2026-05-06 14:10:12 -07:00
kirjs
df54e6a7b2 refactor(forms): use when consistently for conditional rules and validators
This commit updates the signal forms API to use a consistent 'when' parameter for conditional rules and validators, replacing direct function arguments.
2026-05-06 14:10:12 -07:00
Alex Rickabaugh
7745365910 feat(forms): graduate signal forms APIs to public API
Replaced `@experimental` tags with `@publicApi 22.0` across all Signal Forms APIs under `packages/forms/signals` to mark them as ready for general use in v22.

TAG=agy
CONV=0af6c644-225a-4212-a49a-5843d17ec638
2026-05-06 12:01:41 -07:00
Leon Senft
1f30aacbe5 refactor(forms): bind formatted date string to min/max for minDate/maxDate (#68001)
* Test that `minDate`/`maxDate` binds to `min`/`max` on date and time inputs
* Test that `min`/`max` attribute can be set directly on date and time inputs
* Relax type checker to allow `min`/`max` bindings on date and time inputs

PR Close #68001
2026-05-06 11:59:18 -07:00
Leon Senft
276c917b34 refactor(forms): add validation rules for date constraints (#68001)
- Added `minDate()` and `maxDate()` for validating constraints on `Date` inputs.
- `ReadonlyFieldState.min` and `.max` now return
  `Signal<NonNullable<TValue>`. This ensures that `min` and `max` inputs
  on custom controls can accept a reliable type (matching their value
  type).
- Made the `TWrite` type parameter of `MetadataKey` contravariant to
  properly indicate that it's writable.
- Added `LimitKey` as a convenience type for defining validation limit
  metadata (e.g. `MAX_NUMBER`, `MIN_DATE`).
- Added `LimitSelectionKey` which can be used to bind a `LimitKey` with
  value-specific aggregation logic, to a generic metadata key (e.g. use
  `MAX_NUMBER` to aggregate numbers for `MAX`).

PR Close #68001
2026-05-06 11:59:18 -07:00
Leon Senft
592a12d6c9 refactor(forms): remove string support from min and max validation rules (#68001)
The `min` and `max` validation rules previously handled `string` values
to accommodate numbers bound to text inputs. However, this is no longer
necessary as the control binding itself handles the conversion.

This change removes string support from these rules, simplifying the
types to `number | null`. The validation logic has been updated to use
concrete checks (`value === null || Number.isNaN(value)`) to ensure safe
TypeScript narrowing.

Associated tests have been updated to:
- Remove string-specific validation checks.
- Add coverage for text input bindings.
- Add coverage for empty input handling (standard behavior where empty
  sets model to null and skips validation).

BREAKING CHANGE: `min` and `max` validation rules no longer support
string values. Bound values must be numbers or null.

PR Close #68001
2026-05-06 11:59:18 -07:00
Alex Rickabaugh
849dba6c65 fix(forms): implement custom control reset propagation
Introduce a highly decoupled FVC and CVA custom control reset mechanism, and implement the framework-wide automatic `transformedValue` and native controls clearing bridge for both new Signal Forms and legacy forms (Template-driven and Reactive).

1. Custom Control Reset Propagation (Bug #2):
- Establish agnostic custom control resetting via `FormFieldBindingOptions.reset` in `FormField`.
- Ensure that `FieldNode.reset()` unconditionally triggers `writeValue` updates on CVA custom controls.
- Protect against duplicate writes during subsequent change detection updates in `control_cva.ts` by verifying and tracking previous written values in the local bindings cache.

2. Unified Framework-wide FormControl Integration:
- Introduce a monorepo-wide private InjectionToken `ɵFORM_CONTROL_INTEGRATION` and `ɵFormControlIntegration` interface to act as the single, decoupled bridge for hooking up FVC parse errors and receiving control resets across both Signal and legacy forms architectures.
- Simplify Signal Forms: make `FormField` implements `ɵFormControlIntegration` directly, removing the intermediate context object and reducing DI boilerplate down to a clean `useExisting: FormField` provider. Triggers the `onReset` callback directly inside `FormField.reset()`.
- Upgrade Legacy Forms: `NG_CONTROL_INTEGRATION_PROVIDER` provides the renamed token. `NgControl` handles the event subscription internally (`set onReset(callback)`) to recursively listen to `control.events` (`FormResetEvent`) lazily only when assigned, resolving all `FormControl` swapping timing and lifecycle cleanup races automatically.

3. Automatic `transformedValue` and Native Controls Utility Clearing:
- Make `Parser.reset()` method required in the interface for a cleaner and non-defensive execution.
- Wire `transformedValue` into the new integration token `ɵFORM_CONTROL_INTEGRATION` to clear validation parsing states on resets.
- Lazily resets the UI-facing `rawValue` linked signal utilizing the original native `linkedSignal.set` callback (`originalSet`), correctly bypassing the UI-to-model parser loopback and preventing redundant model writes during `reset()`.
- Wire up Native Controls (`control_native.ts\Device`): Hook `parent.onReset` inside native element creation to automatically trigger the native `parser.reset()` and force DOM writes (`setNativeControlValue`) back down to the DOM input value during resets, ensuring native elements with pending parsing validation errors are successfully cleared and synced on form resets.

TAG=agy
CONV=8b4cee1e-2117-42a4-b242-c8ec7bf01752
2026-05-06 10:45:40 -07:00
Suraj Yadav
68c3abbe09 fix(forms): synchronize controls with the model on reset
Synchronize `controlValue` with the model `value` following `reset()`. This
ensures the UI will reflect the form model in cases where a control had a
pending change–delayed by debouncing–at the time it was reset.
2026-05-06 10:45:40 -07:00
Alan Agius
b8d3f36ed9 feat(compiler-cli): add support for Node.js 26.0.0
Updates the supported Node.js engine versions to include Node.js 26.

This allows running the CLI on Node.js 26.0.0 and above while continuing to support active LTS versions.
2026-05-06 09:55:38 -07:00
Matthieu Riegler
3524de29f3 fix(forms): Add support for range type with outside of native bounds
range inputs don't allow value that are outside their min/max ranges.

fixes #68480
2026-05-05 16:26:03 -07:00
Alex Rickabaugh
5835a5e3a7 fix(forms): prevent orphan field crashes in debounceSync and async validation
- Short-circuit `FieldNode.debounceSync()` if the node is orphaned right
  before calling `this.sync()`, preventing unhandled promise rejections
  on dead state reads.
- Include `this.node.structure.isOrphaned()` in `shouldSkipValidation`
  computed signal in `ValidationState`. This safely shields the entire
  validation layer (sync and async errors) from executing on dead nodes during
  in-flight async validator resolutions.
- Append robust reproduction specs to `orphan_repro.spec.ts` for both the
  `debounceSync` and `validateAsync` async race conditions. Include an intentional
  promise resolution workaround for an experimental Angular `core/resource`
  `PendingTasks` leak deadlock bug uncovered during testing.

TAG=agy
CONV=054e0185-f5f0-40e3-9c9b-413309f36cf6
2026-05-05 15:54:42 -07:00
Alex Rickabaugh
3c44d7c90b fix(forms): fix orphan field error on blur during array removal
Explain the race condition: when an item is deleted from a model array, its
DOM element is removed during change detection, which fires a `blur` event
synchronously. The `blur` handler tries to mark the field as touched,
navigating up to `keyInParent` which throws because the item is already gone
from the array in signals state.

Fix by introducing an `isOrphaned` check that short-circuits `markAsTouched`
early, backed by a reactivity-insulated `childrenMap` poll to avoid double
scans and prevent unhandled exceptions.

TAG=agy
CONV=054e0185-f5f0-40e3-9c9b-413309f36cf6

Fixes #66711

Co-Authored-By: Matthieu Riegler <kyro38@gmail.com>
2026-05-05 15:54:42 -07:00
Leon Senft
708631f2c4 fix(forms): prohibit concurrent submits in signal forms
Prohibit concurrent submits in signal forms to prevent duplicate actions and side effects when a submission is already in progress.

If `submit()` is called while a prior submit is in progress for the same field or any of its parents, it returns `false` immediately without running the action again.

This commit also updates the documentation in `form-submission.md` to reflect this behavior.

Fixes #68317
2026-05-05 11:14:03 -07:00
Alex Rickabaugh
9b9769479b perf(forms): shortcut deepSignal writes if value is unchanged
Avoid deep write path traversal and triggering source signal updates
when calling `deepSignal.set(value)` with a new value that is
identical to the current value (`Object.is`).

This shortcuts the entire write path and unnecessary array/object
copying early on. This approach relies on the guarantee that `source`'s
value is non-nullable in the context where `deepSignal` is created and
used.

TAG=agy
CONV=9e5bd277-0d0a-466c-be36-5e3a8e6910be
2026-05-05 09:28:00 -07:00
Alex Rickabaugh
0ea50ffe5a fix(forms): ensure debounced async validators produce pending status during debounce
When using a debounced async validator, the pending status from the internal
debounced resource was not flowing through to the resource created by the
factory. Replicate the 'chain' logic using the new privately exported ɵchain
function to propagate the loading status correctly.

Fixes #68105
2026-05-04 13:03:07 -07:00
Sam Severance
cd20dd07ce refactor(forms): improve clarity in SelectMultipleControlValueAccessor.writeValue
Rename the _optionMap forEach parameter from `o` to `id` and tighten its
type from `any` to `string`, removing the now-redundant `.toString()` call.
2026-05-01 16:02:34 -07:00
Alex Rickabaugh
e0536091f5
perf(forms): optimize reactivity by using shallow array equality
Add `shallowArrayEquals` to computed signals returning arrays of errors or reasons in Signal Forms. This prevents unnecessary downstream invalidations when the content of the arrays remains unchanged.
2026-04-30 15:41:45 -07:00
splincode
54a985c5ca refactor(forms): replace any with unknown in interop control value types
- `CombinedControl.value` and `InteropNgControl.value` getter now return
  `unknown` instead of `any`, matching the actual `ReadonlyFieldState<unknown>`
  return type of `controlValue()`.
- Remove redundant `as any` cast in `cvaControlCreate`: `parent` is typed as
  `FormField<unknown>`, so `state().controlValue` is `WritableSignal<unknown>`
  and accepts `unknown` directly.
2026-04-29 13:35:48 -07:00
Suraj Yadav
f2c6445681 docs(forms): add NG01902 error reference and link to docs
Add the NG01902 (Orphan field in signal forms) documentation page
to the Error Encyclopedia and change the ORPHAN_FIELD_PROPERTY
error code to -1902 so Angular's RuntimeError automatically appends
a link to angular.dev/errors/NG01902 in the thrown error message.
2026-04-28 12:07:46 -07:00
Matthieu Riegler
b2083a7fd2 build: cleanup workspace deps
Some of the deps are move down to the only targets that uses them.
2026-04-23 11:38:26 -07:00
Matthieu Riegler
7f3f3d7da1 ci: remove remainings of saucelabs tests
Those haven't been used for a while.
2026-04-22 14:41:03 -07:00
Alex Rickabaugh
f9f24fc669 feat(forms): shim legacy NG_VALIDATORS into parseErrors for CVA mode (#67943)
- Injected `NG_VALIDATORS` into `FormField` and exposed it via an internal getter.
- Created a `computed` signal in `cvaControlCreate` to run legacy validators and map into standard validation errors without generic `as any` type assertions.
- Intercepted `registerOnValidatorChange` to trigger updates even when the model value remains unchanged (e.g., going from `null` to `null`).
- Added integration tests to verify parse error propagation and reactivity.

PR Close #67943
2026-04-14 18:32:24 +03:00
Alex Rickabaugh
72d3ace03c fix(forms): use controlValue in NgControl for CVA interop (#67943)
use controlValue() instead of value() to ensure that CVA controls see the most recent user input immediately rather than waiting for it to be synchronized after debouncing

PR Close #67943
2026-04-14 18:32:24 +03:00
arturovt
030422850b docs: add documentation for NG1002
Adds a documentation page for the NG01002 runtime error thrown by
FormGroup and FormArray when setValue is called with a value that is
missing an entry for one or more registered controls.

The error code is also changed from positive (1002) to negative (-1002)
so that Angular appends a link to the error reference page in dev mode,
consistent with how other documented errors (e.g. NG01101, NG01203) are
handled.
2026-04-10 10:54:41 +03:00
Alex Rickabaugh
de56d74da3 fix(forms): align FormField CVA selection priority with standard forms
Prioritize custom ControlValueAccessor instances over default or built-in accessors when applying the [formField] directive. This is achieved by directly consuming selectValueAccessor from @angular/forms, ensuring absolute alignment with the precedence rules used across standard Angular form directives.
2026-04-07 14:21:44 -07:00
Alex Rickabaugh
2e9aeea0fe fix(forms): deduplicate writeValue calls in CVA interop
Update bindings['controlValue'] during onChange to track the view value. This allows bindingUpdated to skip writeValue if the model value matches the last seen view value, preventing redundant writes.

Fixes #67847
2026-04-07 14:21:44 -07:00
Harmeet Singh
789c2cd9fb docs(forms): clarify disabled FormArray value behavior
Document that FormArray.value includes only enabled child controls when the array is enabled, but includes all child values when the FormArray itself is disabled.

Fixes #67759
2026-04-03 11:22:33 -07:00
Alex Rickabaugh
394ad0c2a2 fix(forms): allow late-bound input types for signals forms
Ensure that input [type] bindings are evaluated dynamically rather than cached eagerly during initialization. This allows late-bound expressions for input types to correctly apply constraints like min/max and maxLength.

Fixes #66987
2026-04-03 10:18:09 -07:00
Kam
ef7679b7a5 refactor(forms): use strict equality for pending status getter
The `pending` getter in `AbstractControl` used loose equality (`==`)
while all other status getters (`valid`, `invalid`, `disabled`) use
strict equality (`===`). Both sides are strings so behavior is
identical, but this inconsistency would fail strict linting rules.
2026-03-31 13:51:55 +02:00
Alex Rickabaugh
24e52d450d feat(forms): add debounce option to validateAsync and validateHttp
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.
2026-03-25 14:17:52 -07:00
Leon Senft
b5af15a332
test(forms): support directive composition with FormField
Support composing the `FormField` directive with custom controls:
2026-03-25 13:35:02 -07:00
Alex Rickabaugh
16adbbf423 fix(core): ensure custom controls resolve transitive host directives
Custom controls can be modeled using a set of host directives to alias
and expose value and valueChange (or checked/checkedChange) bindings,
as well as native attributes like disabled.

This commit updates initializeCustomControlStatus to correctly identify
host components using mapped inputs/outputs, even when those inputs are
exposed via transitive host directives. It also updates
customControlHasInput so that the custom control presence check correctly
evaluates the exposed inputs across all applied host directives, caching
the result to optimize performance on hot code paths.
2026-03-25 13:00:46 -07:00
Matthieu Riegler
eae8f7e30b feat(core): Set default Component changeDetection strategy to OnPush
The default change detection strategy is now OnPush.

BREAKING CHANGE: Component with undefined `changeDetection` property are now `OnPush` by default. Specify `changeDetection: ChangeDetectionStrategy.Eager` to keep the previous behavior.
2026-03-24 16:25:02 -07:00
Alex Rickabaugh
709f5a390c feat(forms): add FieldState.getError()
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.
2026-03-24 15:07:55 -07:00
Alex Rickabaugh
ee8d2098cb fix(forms): change FieldState optional properties to non-optional | undefined
This improves compatibility with TypeScript's exactOptionalPropertyTypes.

Fixes #67246
2026-03-24 14:51:31 -07:00
Leon Senft
0eeb1b5f03 fix(forms): allow FormRoot to be used without submission options (#67727)
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
2026-03-23 15:41:19 -07:00
Leon Senft
f4a5b42ebe refactor(forms): rename directive files for consistency (#67727)
- **FormField**:  `form_field_directive.ts` -> `form_field.ts`
- **FormRoot**:   `ng_signal_form.ts`       -> `form_root.ts`

PR Close #67727
2026-03-23 15:41:19 -07:00
Modeste ASSIONGBON
2615f35da9 docs: fix js doc of signal forms ignoreValidators option. 2026-03-23 11:21:16 -07:00
Alex Rickabaugh
df8b020299 fix(forms): clear native date inputs correctly in signal forms when changed via native UI
When a native date input gets cleared manually by a user via the internal browser
UI, the element changes from invalid to valid, but no `input` event is emitted.

This commit introduces `InputValidityMonitor`, an injectable service that
intercepts these edge-case native status changes. The monitor dynamically
installs CSP-compliant styles appending specific animation keyframes for
`:valid` and `:invalid` pseudoclasses on native form controls. By attaching an
`animationstart` listener, Angular intercepts these changes immediately and
re-invokes the parser.

Fixes #67300
2026-03-20 15:10:26 -07:00
Alex Rickabaugh
98c5afdb02 perf(forms): lazily instantiate signal form fields
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
2026-03-20 15:09:26 -07:00
Alex Rickabaugh
50e599e73e fix(core): lazy-initialize debounced state to prevent computation cycle
When building a debounced resource, we previously eagerly started tracking the 'source' signal state by instantiating a regular signal. However, if this 'debounced' primitive is initialized in a computation reactive graph (like signal forms 'validateAsync'), reading the current UI source dependency eagerly can induce a cycle if we haven't finished calculating the graph node yet.

This fix uses a 'linkedSignal' block to define the eager 'source' instead. Because linkedSignals are lazy by default, this bypasses the initial eager evaluation, allowing the containing reactive graph to finish forming first without losing our timing logic inside the ambient effect().
2026-03-19 16:18:38 -07:00
Alex Rickabaugh
41b1410cb8 feat(forms): support binding number|null to <input type="text">
`<input type="number">` often does not provide the desired user experience when editing numbers in
a form. MDN even [describes](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number#using_number_inputs)
how text inputs should be used in many cases instead, via `<input type="text" inputmode="numeric">`
or similar configurations. Previously, this did not work with Signal Forms without a custom input
component/directive.

This PR builds support for binding `number|null` models directly to `<input type="text">` native
controls via `[formField]`. When a model has a number or `null` value, signal forms will preserve
that status when the user makes edits/changes. Empty string values are converted to `null`, other
values are parsed as numbers, and a parse error is raised when a non-numeric value is entered.

Note that it's up to the UI developer to configure additional UI affordances such as setting an
appropriate `inputmode`, rejecting non-numeric keypresses, etc.

Fixes #66903
Fixes #66157
2026-03-19 15:26:34 -07:00
Alex Rickabaugh
74f76d8075 feat(forms): add reloadValidation to Signal Forms to manually trigger async validation
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
2026-03-19 15:22:13 -07:00
Alex Rickabaugh
83032e3605 fix(forms): support generic unions in signal form schemas
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
2026-03-17 17:59:36 -06:00
Alex Rickabaugh
c4ce3f345f feat(forms): template & reactive support for FVC
Implement support for `FormUiComponent`s in both Reactive and Template-driven
forms. This allows components that use the new signal-based form control
architecture to be used seamlessly within existing Angular form paradigms.

Key changes:
- Integrated `ɵngControlCreate` and `ɵngControlUpdate` lifecycle hooks into
  `NgModel`, `FormControlDirective`, and `FormControlName`.
- Implemented branching logic to choose between the traditional `ControlValueAccessor` (CVA) path and the new FVC path based on the host element's capabilities.
- Added comprehensive unit tests for FVC integration in both Reactive (`reactive_fvc.spec.ts`) and Template-driven (`template_fvc.spec.ts`) forms, covering:
    - Value synchronization (model -> view and view -> model).
    - Status synchronization (touched, dirty, valid, invalid, pending, required).
    - Error propagation and `parseErrors` support.
    - Fallback behavior to native DOM properties (disabled, required) when FVC inputs are missing.
    - Graceful fallback to CVA when no FVC pattern is detected.
- Refined `NgModel` to correctly handle `required` validation via its existing `RequiredValidator` directive while supporting FVC for other properties.
2026-03-17 13:18:26 -06:00