From b0a4ecc50e7ab408b5ceed0e438b7d7fb4b50d4d Mon Sep 17 00:00:00 2001 From: kirjs Date: Tue, 16 Dec 2025 17:48:11 -0500 Subject: [PATCH] docs(forms): add a Accessing values section (#66111) This explains how to get the value of a compat form PR Close #66111 --- .../app/app.html | 4 +- .../app/app.ts | 6 +- .../app/app.html | 20 +++---- .../compat-form-group-integration/app/app.ts | 6 +- .../content/guide/forms/signals/migration.md | 59 +++++++++++++------ 5 files changed, 58 insertions(+), 37 deletions(-) diff --git a/adev/src/content/examples/signal-forms/src/compat-form-control-integration/app/app.html b/adev/src/content/examples/signal-forms/src/compat-form-control-integration/app/app.html index 3a35c69b18c..59455131650 100644 --- a/adev/src/content/examples/signal-forms/src/compat-form-control-integration/app/app.html +++ b/adev/src/content/examples/signal-forms/src/compat-form-control-integration/app/app.html @@ -2,7 +2,7 @@
@if (f.email().touched() && f.email().invalid()) { @@ -15,7 +15,7 @@
@if (f.password().touched() && f.password().invalid(); as invalid) { diff --git a/adev/src/content/examples/signal-forms/src/compat-form-control-integration/app/app.ts b/adev/src/content/examples/signal-forms/src/compat-form-control-integration/app/app.ts index 9a729538efa..12a76472bcb 100644 --- a/adev/src/content/examples/signal-forms/src/compat-form-control-integration/app/app.ts +++ b/adev/src/content/examples/signal-forms/src/compat-form-control-integration/app/app.ts @@ -1,4 +1,4 @@ -import {signal, Injector, inject, Component, computed} from '@angular/core'; +import {signal, Component, computed} from '@angular/core'; import {FormControl, Validators, AbstractControl} from '@angular/forms'; import {Field} from '@angular/forms/signals'; import {compatForm} from '@angular/forms/signals/compat'; @@ -34,9 +34,7 @@ export class App { }); // 3. Create the form - readonly f = compatForm(this.user, { - injector: inject(Injector), - }); + readonly f = compatForm(this.user); // We have to manually extract values, because JSON pipe can't serialize FormControl readonly formValue = computed(() => ({ diff --git a/adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.html b/adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.html index 9b366eed70d..94959ec1cc6 100644 --- a/adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.html +++ b/adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.html @@ -4,7 +4,7 @@
@if (f.customerName().touched() && f.customerName().invalid()) { @@ -21,7 +21,7 @@
@if (street.touched && street.invalid) {
@@ -34,12 +34,12 @@
@if (city.touched && city.invalid) { -
-

City is required

-
+
+

City is required

+
}
@@ -47,12 +47,12 @@
@if (zip.touched && zip.invalid) { -
-

Zip Code is required

-
+
+

Zip Code is required

+
}
diff --git a/adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.ts b/adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.ts index 58c909f3227..6619afe8828 100644 --- a/adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.ts +++ b/adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.ts @@ -1,4 +1,4 @@ -import {signal, Injector, inject, Component, computed} from '@angular/core'; +import {signal, Component, computed} from '@angular/core'; import {FormControl, FormGroup, Validators, ReactiveFormsModule} from '@angular/forms'; import {Field} from '@angular/forms/signals'; import {compatForm} from '@angular/forms/signals/compat'; @@ -18,10 +18,10 @@ export class App { zip: new FormControl('94043', Validators.required), }); - // 2. Wrap it inside a new Signal Form state + // 2. Include it in the state like it's a value readonly checkoutModel = signal({ customerName: '', - shippingAddress: this.addressGroup, // The bridge handles the nesting + shippingAddress: this.addressGroup, }); // 3. Create the form diff --git a/adev/src/content/guide/forms/signals/migration.md b/adev/src/content/guide/forms/signals/migration.md index 584c1f2317e..c22328c2643 100644 --- a/adev/src/content/guide/forms/signals/migration.md +++ b/adev/src/content/guide/forms/signals/migration.md @@ -8,19 +8,19 @@ legacy Reactive Forms. Sometimes you may want to use existing reactive `FormControl` instances within a Signal Form. This is useful for controls that involve: -* Complex asynchronous logic. -* Intricate RxJS operators that are not yet ported. -* Integration with legacy third-party libraries. +- Complex asynchronous logic. +- Intricate RxJS operators that are not yet ported. +- Integration with legacy third-party libraries. ### Integrating a `FormControl` into a signal form Consider an existing `passwordControl` that uses a specialized `enterprisePasswordValidator`. Instead of rewriting the validator, you can bridge the control into your signal state. -First, define your legacy control: +We can do it using `compatForm` ```typescript -import {signal, Injector, inject} from '@angular/core'; +import {signal} from '@angular/core'; import {FormControl, Validators} from '@angular/forms'; import {compatForm} from '@angular/forms/signals/compat'; @@ -37,10 +37,7 @@ const user = signal({ }); // 3. Create the form -const f = compatForm(user, { - injector: inject(Injector), -}); - +const f = compatForm(user); // Access values via the signal tree console.log(f.email().value()); // Current email value @@ -90,13 +87,13 @@ In the template, use standard reactive syntax by binding the underlying control: -## Integrating a `FormGroup` into a signal form +### Integrating a `FormGroup` into a signal form You can also wrap an entire `FormGroup`. This is common when a reusable sub-section of a form—such as an **Address Block **—is still managed by legacy Reactive Forms. ```typescript -import {signal, Injector, inject} from '@angular/core'; +import {signal} from '@angular/core'; import {FormGroup, FormControl, Validators} from '@angular/forms'; import {compatForm} from '@angular/forms/signals/compat'; @@ -104,18 +101,16 @@ import {compatForm} from '@angular/forms/signals/compat'; const addressGroup = new FormGroup({ street: new FormControl('123 Angular Way', Validators.required), city: new FormControl('Mountain View', Validators.required), - zip: new FormControl('94043', Validators.required) + zip: new FormControl('94043', Validators.required), }); -// 2. Wrap it inside a new Signal Form state +// 2. Include it in the state like it's a value const checkoutModel = signal({ customerName: 'Pirojok the Cat', - shippingAddress: addressGroup // The bridge handles the nesting + shippingAddress: addressGroup, }); -const f = compatForm(checkoutModel, { - injector: inject(Injector) -}); +const f = compatForm(checkoutModel); ``` The `shippingAddress` field acts as a branch in your Signal Form tree. You can bind these nested controls in your @@ -182,10 +177,38 @@ template by accessing the underlying legacy controls via `.control()`: ``` + +### Accessing values + +While `compatForm` proxies value access on the `FormControl` level, full form value would preserve the control: + +```typescript +const passwordControl = new FormControl('password' /** ... */); + +const user = signal({ + email: '', + password: passwordControl, // Nest the legacy control directly +}); + +const form = compatForm(user); +form.password().value(); // 'password' +form().value(); // { email: '', password: FormControl} +``` + +If you need the whole form value, you'd have to build it manually: + +```typescript +const formValue = computed(() => ({ + email: form.email().value(), + password: form.password().value(), +})); // {email: '', password: ''} +``` + ## Bottom-up migration -This is coming soon. \ No newline at end of file + +This is coming soon.