mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
docs(forms): add a Accessing values section (#66111)
This explains how to get the value of a compat form PR Close #66111
This commit is contained in:
parent
65a2a0da8f
commit
b0a4ecc50e
5 changed files with 58 additions and 37 deletions
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<label>
|
||||
Email:
|
||||
<input [field]="f.email">
|
||||
<input [field]="f.email" />
|
||||
</label>
|
||||
|
||||
@if (f.email().touched() && f.email().invalid()) {
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<div>
|
||||
<label>
|
||||
Password:
|
||||
<input [field]="f.password" type="password">
|
||||
<input [field]="f.password" type="password" />
|
||||
</label>
|
||||
|
||||
@if (f.password().touched() && f.password().invalid(); as invalid) {
|
||||
|
|
|
|||
|
|
@ -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(() => ({
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<div>
|
||||
<label>
|
||||
Customer Name:
|
||||
<input [field]="f.customerName">
|
||||
<input [field]="f.customerName" />
|
||||
</label>
|
||||
|
||||
@if (f.customerName().touched() && f.customerName().invalid()) {
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<div>
|
||||
<label>
|
||||
Street:
|
||||
<input [formControl]="street">
|
||||
<input [formControl]="street" />
|
||||
</label>
|
||||
@if (street.touched && street.invalid) {
|
||||
<div class="error-list">
|
||||
|
|
@ -34,12 +34,12 @@
|
|||
<div>
|
||||
<label>
|
||||
City:
|
||||
<input [formControl]="city">
|
||||
<input [formControl]="city" />
|
||||
</label>
|
||||
@if (city.touched && city.invalid) {
|
||||
<div class="error-list">
|
||||
<p>City is required</p>
|
||||
</div>
|
||||
<div class="error-list">
|
||||
<p>City is required</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
@ -47,12 +47,12 @@
|
|||
<div>
|
||||
<label>
|
||||
Zip Code:
|
||||
<input [formControl]="zip">
|
||||
<input [formControl]="zip" />
|
||||
</label>
|
||||
@if (zip.touched && zip.invalid) {
|
||||
<div class="error-list">
|
||||
<p>Zip Code is required</p>
|
||||
</div>
|
||||
<div class="error-list">
|
||||
<p>Zip Code is required</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
|||
<docs-code header="app.html" path="adev/src/content/examples/signal-forms/src/compat-form-control-integration/app/app.html"/>
|
||||
</docs-code-multifile>
|
||||
|
||||
## 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()`:
|
|||
</fieldset>
|
||||
</form>
|
||||
```
|
||||
|
||||
<docs-code-multifile preview path="adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.ts">
|
||||
<docs-code header="app.ts" path="adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.ts"/>
|
||||
<docs-code header="app.html" path="adev/src/content/examples/signal-forms/src/compat-form-group-integration/app/app.html"/>
|
||||
</docs-code-multifile>
|
||||
|
||||
### 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.
|
||||
|
||||
This is coming soon.
|
||||
|
|
|
|||
Loading…
Reference in a new issue