docs: iterate on signal forms essential guide

This commit is contained in:
Ben Hong 2025-11-12 10:32:33 -05:00 committed by Andrew Kushnir
parent a55a96a5fb
commit d02374a45c
7 changed files with 36 additions and 26 deletions

View file

@ -6,8 +6,8 @@ import {form, Field} from '@angular/forms/signals';
*/
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrl: 'app.component.css',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Field],
})
export class App {

View file

@ -6,8 +6,8 @@ import {form, Field, required, email, submit} from '@angular/forms/signals';
*/
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrl: 'app.component.css',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Field],
})
export class App {

View file

@ -2,11 +2,15 @@
Signal Forms is built on Angular signals to provide a reactive, type-safe way to manage form state.
</docs-decorative-header>
## How it works
Signal Forms manage form state using Angular signals to provide automatic synchronization between your data model and the UI.
### 1. Create a signal model
This guide walks you through the core concepts to create forms with Signal Forms. Here's how it works:
When you create a form, you start by creating a signal that holds the state of your form:
## Creating your first form
### 1. Create a form model
When you create a form, you start by creating a signal that holds your form's data:
```ts
const loginModel = signal({
@ -15,9 +19,9 @@ const loginModel = signal({
});
```
### 2. Pass the data model to `form()`
### 2. Pass the form model to `form()`
Then, you pass your form model into the `form()` function to create a form tree that mirrors and enhances your model's structure:
Then, you pass your form model into the `form()` function to create a **field tree** - an object structure that mirrors your model's shape, allowing you to access fields with dot notation:
```ts
form(loginModel);
@ -46,11 +50,17 @@ loginForm.email().value.set('alice@wonderland.com');
console.log(loginModel().email); // 'alice@wonderland.com'
```
NOTE: The `[field]` directive also syncs field state for attributes like `required`, `disabled`, and `readonly` when appropriate. You can read more in the upcoming in-depth guide.
NOTE: The `[field]` directive also syncs field state for attributes like `required`, `disabled`, and `readonly` when appropriate.
### 4. Read form field values with `value()`
Finally, you can access field values as reactive signals by calling the field as a function and accessing its `value()`:
You can access field state by calling the field as a function. This returns a `FieldState` object containing reactive signals for the field's value, validation status, and interaction state:
```ts
loginForm.email() // Returns FieldState with value(), valid(), touched(), etc.
```
To read the field's current value, access the `value()` signal:
```html
<!-- Render form value that updates automatically as user types -->
@ -64,10 +74,10 @@ const currentEmail = loginForm.email().value();
Here's a complete example:
<docs-code-multifile preview path="adev/src/content/examples/signal-forms/src/login-simple/app/app.component.ts">
<docs-code header="app/app.component.ts" path="adev/src/content/examples/signal-forms/src/login-simple/app/app.component.ts"/>
<docs-code header="app/app.component.html" path="adev/src/content/examples/signal-forms/src/login-simple/app/app.component.html"/>
<docs-code header="app/app.component.css" path="adev/src/content/examples/signal-forms/src/login-simple/app/app.component.css"/>
<docs-code-multifile preview path="adev/src/content/examples/signal-forms/src/login-simple/app/app.ts">
<docs-code header="app.ts" path="adev/src/content/examples/signal-forms/src/login-simple/app/app.ts"/>
<docs-code header="app.html" path="adev/src/content/examples/signal-forms/src/login-simple/app/app.html"/>
<docs-code header="app.css" path="adev/src/content/examples/signal-forms/src/login-simple/app/app.css"/>
</docs-code-multifile>
## Basic usage
@ -187,12 +197,12 @@ NOTE: Multiple select (`<select multiple>`) is not supported by the `[field]` di
## Validation and state
Signal Forms provides built-in validators that you can apply to your form fields. To add validation, pass a schema function as the second argument to `form()`. This function receives a field **path** (sometimes abbreviated as `p`) that allows you to reference the model and its subsequent fields:
Signal Forms provides built-in validators that you can apply to your form fields. To add validation, pass a schema function as the second argument to `form()`. This function receives a **field path** parameter that allows you to reference the fields in your form model:
```ts
const loginForm = form(loginModel, (p) => {
required(p.email);
email(p.email);
const loginForm = form(loginModel, (fieldPath) => {
required(fieldPath.email);
email(fieldPath.email);
});
```
@ -211,19 +221,19 @@ required(p.email, { message: 'Email is required' });
email(p.email, { message: 'Please enter a valid email address' });
```
Each form field exposes its validation state through signals. For example, you can check `field.valid()` to see if validation passes, `field.touched()` to see if the user has interacted with it, and `field.errors()` to get the list of validation errors.
Each form field exposes its validation state through signals. For example, you can check `field().valid()` to see if validation passes, `field().touched()` to see if the user has interacted with it, and `field().errors()` to get the list of validation errors.
Here's a complete example:
<docs-code-multifile preview path="adev/src/content/examples/signal-forms/src/login-validation/app/app.component.ts">
<docs-code header="app/app.component.ts" path="adev/src/content/examples/signal-forms/src/login-validation/app/app.component.ts"/>
<docs-code header="app/app.component.html" path="adev/src/content/examples/signal-forms/src/login-validation/app/app.component.html"/>
<docs-code header="app/app.component.css" path="adev/src/content/examples/signal-forms/src/login-validation/app/app.component.css"/>
<docs-code-multifile preview path="adev/src/content/examples/signal-forms/src/login-validation/app/app.ts">
<docs-code header="app.ts" path="adev/src/content/examples/signal-forms/src/login-validation/app/app.ts"/>
<docs-code header="app.html" path="adev/src/content/examples/signal-forms/src/login-validation/app/app.html"/>
<docs-code header="app.css" path="adev/src/content/examples/signal-forms/src/login-validation/app/app.css"/>
</docs-code-multifile>
### Field State Signals
Each field provides these state signals:
Every `field()` provides these state signals:
| State | Description |
| ------------ | -------------------------------------------------------------------------- |
@ -234,4 +244,4 @@ Each field provides these state signals:
| `pending()` | Returns `true` if async validation is in progress |
| `errors()` | Returns an array of validation errors with `kind` and `message` properties |
TIP: Show errors only after `touched()` is true to avoid displaying validation messages before the user has interacted with a field.
TIP: Show errors only after `field().touched()` is true to avoid displaying validation messages before the user has interacted with a field.