Skip to content

Commit 90bd469

Browse files
bencodezenthePunderWoman
authored andcommitted
docs: iterate on signal forms essential guide
1 parent 758cd11 commit 90bd469

10 files changed

Lines changed: 49 additions & 235 deletions

File tree

adev/src/content/examples/signal-forms/src/login-simple/app/app.component.css

Lines changed: 0 additions & 27 deletions
This file was deleted.

adev/src/content/examples/signal-forms/src/login-simple/app/app.component.html

Lines changed: 0 additions & 14 deletions
This file was deleted.

adev/src/content/examples/signal-forms/src/login-simple/app/app.component.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

adev/src/content/examples/signal-forms/src/login-simple/app/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Component, signal} from '@angular/core';
1+
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
22
import {form, Field} from '@angular/forms/signals';
33

44
interface LoginData {
@@ -11,6 +11,7 @@ interface LoginData {
1111
templateUrl: 'app.html',
1212
styleUrl: 'app.css',
1313
imports: [Field],
14+
changeDetection: ChangeDetectionStrategy.OnPush,
1415
})
1516
export class App {
1617
loginModel = signal<LoginData>({

adev/src/content/examples/signal-forms/src/login-validation/app/app.component.css

Lines changed: 0 additions & 70 deletions
This file was deleted.

adev/src/content/examples/signal-forms/src/login-validation/app/app.component.html

Lines changed: 0 additions & 33 deletions
This file was deleted.

adev/src/content/examples/signal-forms/src/login-validation/app/app.component.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

adev/src/content/examples/signal-forms/src/login-validation/app/app.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<input type="email" [field]="loginForm.email" />
66
</label>
77

8-
@if (loginForm.email().touched() && loginForm.email().invalid()) {
8+
@if (loginForm.email().invalid()) {
99
<ul class="error-list">
1010
@for (error of loginForm.email().errors(); track $index) {
1111
<li>{{ error.message }}</li>
@@ -20,7 +20,7 @@
2020
<input type="password" [field]="loginForm.password" />
2121
</label>
2222

23-
@if (loginForm.password().touched() && loginForm.password().invalid()) {
23+
@if (loginForm.password().invalid()) {
2424
<div class="error">
2525
@for (error of loginForm.password().errors(); track $index) {
2626
<p>{{ error.message }}</p>
Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {Component, signal} from '@angular/core';
2-
import {form, Field, required, email, submit} from '@angular/forms/signals';
1+
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
2+
import {form, Field, required, email, debounce} from '@angular/forms/signals';
33

44
interface LoginData {
55
email: string;
@@ -11,26 +11,28 @@ interface LoginData {
1111
templateUrl: 'app.html',
1212
styleUrl: 'app.css',
1313
imports: [Field],
14+
changeDetection: ChangeDetectionStrategy.OnPush,
1415
})
1516
export class App {
1617
loginModel = signal<LoginData>({
1718
email: '',
1819
password: '',
1920
});
2021

21-
loginForm = form(this.loginModel, (p) => {
22-
required(p.email, {message: 'Email is required'});
23-
email(p.email, {message: 'Enter a valid email address'});
24-
required(p.password, {message: 'Password is required'});
22+
loginForm = form(this.loginModel, (schemaPath) => {
23+
debounce(schemaPath.email, 500);
24+
required(schemaPath.email, {message: 'Email is required'});
25+
email(schemaPath.email, {message: 'Enter a valid email address'});
26+
27+
debounce(schemaPath.password, 500);
28+
required(schemaPath.password, {message: 'Password is required'});
2529
});
2630

2731
onSubmit(event: Event) {
2832
event.preventDefault();
29-
submit(this.loginForm, async () => {
30-
// Perform login logic here
31-
const credentials = this.loginModel();
32-
console.log('Logging in with:', credentials);
33-
// e.g., await this.authService.login(credentials);
34-
});
33+
// Perform login logic here
34+
const credentials = this.loginModel();
35+
console.log('Logging in with:', credentials);
36+
// e.g., await this.authService.login(credentials);
3537
}
3638
}

adev/src/content/introduction/essentials/signal-forms.md

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This guide walks you through the core concepts to create forms with Signal Forms
88

99
## Creating your first form
1010

11-
### 1. Create a form model
11+
### 1. Create a form model with `signal()`
1212

1313
Every form starts by creating a signal that holds your form's data model:
1414

@@ -24,19 +24,19 @@ const loginModel = signal<LoginData>({
2424
});
2525
```
2626

27-
### 2. Pass the form model to `form()`
27+
### 2. Pass the form model to `form()` to create a `FieldTree`
2828

2929
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:
3030

3131
```ts
32-
form(loginModel);
32+
const loginForm = form(loginModel);
3333

3434
// Access fields directly by property name
3535
loginForm.email
3636
loginForm.password
3737
```
3838

39-
### 3. Bind inputs with `[field]` directive
39+
### 3. Bind HTML inputs with `[field]` directive
4040

4141
Next, you bind your HTML inputs to the form using the `[field]` directive, which creates two-way binding between them:
4242

@@ -45,19 +45,11 @@ Next, you bind your HTML inputs to the form using the `[field]` directive, which
4545
<input type="password" [field]="loginForm.password" />
4646
```
4747

48-
As a result, user changes (such as typing in the field) automatically updates the form, and any programmatic changes update the displayed value as well:
49-
50-
```ts
51-
// Update the value programmatically
52-
loginForm.email().value.set('alice@wonderland.com');
53-
54-
// The model signal is also updated
55-
console.log(loginModel().email); // 'alice@wonderland.com'
56-
```
48+
As a result, user changes (such as typing in the field) automatically updates the form.
5749

5850
NOTE: The `[field]` directive also syncs field state for attributes like `required`, `disabled`, and `readonly` when appropriate.
5951

60-
### 4. Read form field values with `value()`
52+
### 4. Read field values with `value()`
6153

6254
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:
6355

@@ -77,6 +69,22 @@ To read the field's current value, access the `value()` signal:
7769
const currentEmail = loginForm.email().value();
7870
```
7971

72+
### 5. Update field values with `set()`
73+
74+
You can programmatically update a field's value using the `value.set()` method. This updates both the field and the underlying model signal:
75+
76+
```ts
77+
// Update the value programmatically
78+
loginForm.email().value.set('alice@wonderland.com');
79+
```
80+
81+
As a result, both the field value and the model signal are updated automatically:
82+
83+
```ts
84+
// The model signal is also updated
85+
console.log(loginModel().email); // 'alice@wonderland.com'
86+
```
87+
8088
Here's a complete example:
8189

8290
<docs-code-multifile preview path="adev/src/content/examples/signal-forms/src/login-simple/app/app.ts">
@@ -202,16 +210,17 @@ NOTE: Multiple select (`<select multiple>`) is not supported by the `[field]` di
202210

203211
## Validation and state
204212

205-
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 **FieldPath** parameter that allows you to reference the fields in your form model:
213+
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 **schema path** parameter that allows you to reference the fields in your form model:
206214

207215
```ts
208-
const loginForm = form(loginModel, (fieldPath) => {
209-
required(fieldPath.email);
210-
email(fieldPath.email);
216+
const loginForm = form(loginModel, (schemaPath) => {
217+
debounce(schemaPath.email, 500);
218+
required(schemaPath.email);
219+
email(schemaPath.email);
211220
});
212221
```
213222

214-
NOTE: FieldPath only mirrors the shape of your data and does not allow you to access value or any other state.
223+
NOTE: The schema path parameter provides paths to your fields for applying validation rules. To access field values and state, use the field tree (such as `loginForm.email()`).
215224

216225
Common validators include:
217226

@@ -224,8 +233,8 @@ Common validators include:
224233
You can also customize error messages by passing an options object as the second argument to the validator:
225234

226235
```ts
227-
required(p.email, { message: 'Email is required' });
228-
email(p.email, { message: 'Please enter a valid email address' });
236+
required(schemaPath.email, { message: 'Email is required' });
237+
email(schemaPath.email, { message: 'Please enter a valid email address' });
229238
```
230239

231240
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.
@@ -251,4 +260,4 @@ Every `field()` provides these state signals:
251260
| `pending()` | Returns `true` if async validation is in progress |
252261
| `errors()` | Returns an array of validation errors with `kind` and `message` properties |
253262

254-
TIP: Show errors only after `field().touched()` is true to avoid displaying validation messages before the user has interacted with a field.
263+
TIP: Use the `debounce()` validation rule to delay error display until the user stops typing or leaves the field. This prevents showing errors while the user is still entering their input.

0 commit comments

Comments
 (0)