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.
This commit is contained in:
Alex Rickabaugh
2026-01-27 17:33:10 -08:00
committed by Matthew Beck (Berry)
parent b2f08574e8
commit c4ce3f345f
14 changed files with 1405 additions and 12 deletions
@@ -9,8 +9,10 @@ function TestCmp_ng_template_1_Template(rf, ctx) {
return $r3$.ɵɵresetView($event);
});
$r3$.ɵɵelementEnd();
$r3$.ɵɵcontrolCreate();
} if (rf & 2) {
const $ctx_r0$ = $r3$.ɵɵnextContext();
$r3$.ɵɵtwoWayProperty("ngModel", $ctx_r0$.name);
$r3$.ɵɵcontrol();
}
}
@@ -7,8 +7,10 @@ function TestCmp_Template(rf, ctx) {
return $event;
});
$r3$.ɵɵelementEnd();
$r3$.ɵɵcontrolCreate();
} if (rf & 2) {
$r3$.ɵɵadvance();
$r3$.ɵɵtwoWayProperty("ngModel", ctx.name);
$r3$.ɵɵcontrol();
}
}
@@ -8,10 +8,12 @@ function TestCmp_For_1_Template(rf, ctx) {
return $r3$.ɵɵresetView($event);
});
$r3$.ɵɵelementEnd();
$r3$.ɵɵcontrolCreate();
}
if (rf & 2) {
const $name_r2$ = ctx.$implicit;
$r3$.ɵɵtwoWayProperty("ngModel", $name_r2$);
$r3$.ɵɵcontrol();
}
}
@@ -6,8 +6,10 @@ function TestCmp_Template(rf, ctx) {
return $event;
});
$r3$.ɵɵelementEnd();
$r3$.ɵɵcontrolCreate();
}
if (rf & 2) {
$r3$.ɵɵtwoWayProperty("ngModel", ctx.value);
$r3$.ɵɵcontrol();
}
}