Skip to content

Commit 3e61454

Browse files
committed
fixup! docs(forms): add initial signal forms examples
1 parent 4eaf4a0 commit 3e61454

File tree

3 files changed

+55
-44
lines changed

3 files changed

+55
-44
lines changed

packages/forms/signals/examples/01-basic-signal-form.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ Use this pattern as the starting point for any new form in an Angular applicatio
2525

2626
- **`form()`:** A function that creates a `Field` representing the form, taking a `WritableSignal` as the data model.
2727
- **`[control]` directive:** Binds a `Field` from your component to a native HTML input element, creating a two-way data binding.
28-
- **`@Output()`:** A decorator used to emit custom events from a child component to a parent component.
29-
- **`(ngSubmit)`:** An event binding on a `<form>` element that triggers a method when the form is submitted.
28+
- **`submit()`:** An async helper function that manages the form's submission state and should be called from the submit event handler.
3029

3130
## Example Files
3231

@@ -38,7 +37,7 @@ This file defines the component's logic, including the signal-based data model a
3837

3938
```typescript
4039
import { Component, signal, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
41-
import { form } from '@angular/forms/signals';
40+
import { form, submit } from '@angular/forms/signals';
4241
import { JsonPipe } from '@angular/common';
4342

4443
export interface UserProfile {
@@ -65,20 +64,22 @@ export class UserProfileComponent {
6564

6665
profileForm = form(this.profileModel);
6766

68-
handleSubmit() {
67+
async handleSubmit() {
6968
// For this basic example, we assume the form is always valid for submission.
7069
// See the validation example for how to handle invalid forms.
71-
this.submitted.emit(this.profileForm().value());
70+
await submit(this.profileForm, async () => {
71+
this.submitted.emit(this.profileForm().value());
72+
});
7273
}
7374
}
7475
```
7576

7677
### user-profile.component.html
7778

78-
The template is wrapped in a `<form>` tag with an `(ngSubmit)` event and a submit button.
79+
The template is wrapped in a `<form>` tag with a `(submit)` event and a submit button.
7980

8081
```html
81-
<form (ngSubmit)="handleSubmit()">
82+
<form (submit)="handleSubmit(); $event.preventDefault()">
8283
<div>
8384
<label>
8485
First Name:
@@ -109,8 +110,8 @@ The template is wrapped in a `<form>` tag with an `(ngSubmit)` event and a submi
109110

110111
- The `[control]` directive automatically handles the two-way data binding between the input element and the corresponding `Field` in the `profileForm`.
111112
- The `profileForm()` signal gives you access to the `FieldState`, which includes the form's `value` as a signal.
112-
- The `(ngSubmit)` event on the `<form>` element is bound to the `handleSubmit` method in the component.
113-
- When `handleSubmit` is called, it emits the current form value through the `submitted` output property.
113+
- The native `(submit)` event on the `<form>` element is bound to the `handleSubmit` method. It's important to call `$event.preventDefault()` to prevent a full page reload.
114+
- The `handleSubmit` method uses the `submit()` helper to manage the submission process.
114115

115116
## How to Use This Example
116117

packages/forms/signals/examples/02-built-in-validators.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,8 @@ Use this pattern as the primary method for applying common validation logic in y
3131

3232
- **`required()`:** A built-in validator that ensures a field has a value.
3333
- **`minLength(number)`:** A built-in validator that ensures a string's length is at least the specified minimum.
34-
- **`maxLength(number)`:** A built-in validator that ensures a string's length does not exceed the specified maximum.
35-
- **`min(number)`:** A built-in validator that ensures a numeric value is at least the specified minimum.
36-
- **`max(number)`:** A built-in validator that ensures a numeric value does not exceed the specified maximum.
3734
- **`email()`:** A built-in validator that checks if a string is in a valid email format.
38-
- **`pattern(RegExp)`:** A built-in validator that checks if a string matches a given regular expression.
35+
- **`submit()`:** An async helper function that manages the form's submission state and should be called from the submit event handler.
3936

4037
## Example Files
4138

@@ -47,7 +44,7 @@ This file defines the component's logic, including the data model and a validati
4744

4845
```typescript
4946
import { Component, signal, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
50-
import { form, schema } from '@angular/forms/signals';
47+
import { form, schema, submit } from '@angular/forms/signals';
5148
import { required, minLength, maxLength, min, max, email, pattern } from '@angular/forms/signals';
5249
import { JsonPipe } from '@angular/common';
5350

@@ -92,10 +89,10 @@ export class UserSettingsComponent {
9289

9390
settingsForm = form(this.settingsModel, settingsSchema);
9491

95-
handleSubmit() {
96-
if (this.settingsForm().valid()) {
92+
async handleSubmit() {
93+
await submit(this.settingsForm, async () => {
9794
this.submitted.emit(this.settingsForm().value());
98-
}
95+
});
9996
}
10097
}
10198
```
@@ -105,16 +102,18 @@ export class UserSettingsComponent {
105102
This file provides the template for the form, displaying specific error messages for each potential validation failure.
106103

107104
```html
108-
<form (ngSubmit)="handleSubmit()">
105+
<form (submit)="handleSubmit(); $event.preventDefault()">
109106
<div>
110107
<label>Username:</label>
111108
<input type="text" [control]="settingsForm.username" />
112109
@if (settingsForm.username().errors().length > 0) {
113110
<div class="errors">
114111
@for (error of settingsForm.username().errors()) {
115-
@if (error.required) { <p>Username is required.</p> }
116-
@if (error.minLength) { <p>Username must be at least 3 characters.</p> }
117-
@if (error.maxLength) { <p>Username cannot exceed 20 characters.</p> }
112+
@switch (error.kind) {
113+
@case ('required') { <p>Username is required.</p> }
114+
@case ('minLength') { <p>Username must be at least 3 characters.</p> }
115+
@case ('maxLength') { <p>Username cannot exceed 20 characters.</p> }
116+
}
118117
}
119118
</div>
120119
}
@@ -126,9 +125,11 @@ This file provides the template for the form, displaying specific error messages
126125
@if (settingsForm.age().errors().length > 0) {
127126
<div class="errors">
128127
@for (error of settingsForm.age().errors()) {
129-
@if (error.required) { <p>Age is required.</p> }
130-
@if (error.min) { <p>You must be at least 18 years old.</p> }
131-
@if (error.max) { <p>Age cannot be more than 100.</p> }
128+
@switch (error.kind) {
129+
@case ('required') { <p>Age is required.</p> }
130+
@case ('min') { <p>You must be at least 18 years old.</p> }
131+
@case ('max') { <p>Age cannot be more than 100.</p> }
132+
}
132133
}
133134
</div>
134135
}
@@ -140,8 +141,10 @@ This file provides the template for the form, displaying specific error messages
140141
@if (settingsForm.email().errors().length > 0) {
141142
<div class="errors">
142143
@for (error of settingsForm.email().errors()) {
143-
@if (error.required) { <p>Email is required.</p> }
144-
@if (error.email) { <p>Please enter a valid email address.</p> }
144+
@switch (error.kind) {
145+
@case ('required') { <p>Email is required.</p> }
146+
@case ('email') { <p>Please enter a valid email address.</p> }
147+
}
145148
}
146149
</div>
147150
}
@@ -153,7 +156,9 @@ This file provides the template for the form, displaying specific error messages
153156
@if (settingsForm.website().errors().length > 0) {
154157
<div class="errors">
155158
@for (error of settingsForm.website().errors()) {
156-
@if (error.pattern) { <p>Please enter a valid URL.</p> }
159+
@switch (error.kind) {
160+
@case ('pattern') { <p>Please enter a valid URL.</p> }
161+
}
157162
}
158163
</div>
159164
}
@@ -166,7 +171,8 @@ This file provides the template for the form, displaying specific error messages
166171
## Usage Notes
167172

168173
- All built-in validators are imported directly from `@angular/forms`.
169-
- Each validator is a function that takes the `FieldPath` as its first argument, and optional configuration (like the minimum length) as the second.
174+
- The native `(submit)` event on the `<form>` element is bound to the `handleSubmit` method. It's important to call `$event.preventDefault()` to prevent a full page reload.
175+
- The `handleSubmit` method uses the `submit()` helper to manage the submission process.
170176

171177
## How to Use This Example
172178

packages/forms/signals/examples/03-cross-field-validation.md

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Signal Form with Cross-Field Validation
3-
summary: Implements a cross-field validator by applying a validator to one field that reactively depends on the value of another field.
3+
summary: Implements a cross-field validator by applying a validator to one field that reactively depends on the value of another field using `valueOf`.
44
keywords:
55
- signal forms
66
- form
@@ -29,7 +29,7 @@ Use this pattern when the validity of one form field depends on the value of ano
2929

3030
- **`validate()`:** A function used within a schema to add a validator to a specific field.
3131
- **`valueOf()`:** A function available in the validation context that allows you to reactively access the value of any other field in the form.
32-
- **`valid` signal:** A signal on the `FieldState` that is `true` only when the field and all its descendants are valid.
32+
- **`submit()`:** An async helper function that manages the form's submission state and should be called from the submit event handler.
3333

3434
## Example Files
3535

@@ -41,7 +41,7 @@ This file defines the component's logic. The schema applies a validator to `conf
4141

4242
```typescript
4343
import { Component, signal, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
44-
import { form, schema, validate } from '@angular/forms/signals';
44+
import { form, schema, validate, requiredError, minLengthError, customError, submit } from '@angular/forms/signals';
4545
import { JsonPipe } from '@angular/common';
4646

4747
export interface PasswordForm {
@@ -51,14 +51,14 @@ export interface PasswordForm {
5151

5252
const passwordSchema = schema<PasswordForm>((passwordForm) => {
5353
validate(passwordForm.password, ({value}) => {
54-
if (value === '') return { required: true };
55-
if (value.length < 8) return { minLength: { requiredLength: 8, actualLength: value.length } };
54+
if (value() === '') return requiredError();
55+
if (value().length < 8) return minLengthError(8);
5656
return null;
5757
});
5858

5959
validate(passwordForm.confirmPassword, ({value, valueOf}) => {
60-
if (value !== valueOf(passwordForm.password)) {
61-
return { mismatch: true };
60+
if (value() !== valueOf(passwordForm.password)) {
61+
return customError({ kind: 'mismatch' });
6262
}
6363
return null;
6464
});
@@ -81,10 +81,10 @@ export class PasswordFormComponent {
8181

8282
passwordForm = form(this.passwordModel, passwordSchema);
8383

84-
handleSubmit() {
85-
if (this.passwordForm().valid()) {
84+
async handleSubmit() {
85+
await submit(this.passwordForm, async () => {
8686
this.submitted.emit(this.passwordForm().value());
87-
}
87+
});
8888
}
8989
}
9090
```
@@ -94,7 +94,7 @@ export class PasswordFormComponent {
9494
This file provides the template for the form, showing how to display errors for both field-level and cross-field validation rules.
9595

9696
```html
97-
<form (ngSubmit)="handleSubmit()">
97+
<form (submit)="handleSubmit(); $event.preventDefault()">
9898
<div>
9999
<label>
100100
Password:
@@ -103,8 +103,10 @@ This file provides the template for the form, showing how to display errors for
103103
@if (passwordForm.password().errors().length > 0) {
104104
<div class="errors">
105105
@for (error of passwordForm.password().errors()) {
106-
@if (error.required) { <p>Password is required.</p> }
107-
@if (error.minLength) { <p>Password must be at least 8 characters long.</p> }
106+
@switch (error.kind) {
107+
@case ('required') { <p>Password is required.</p> }
108+
@case ('minLength') { <p>Password must be at least 8 characters long.</p> }
109+
}
108110
}
109111
</div>
110112
}
@@ -117,7 +119,9 @@ This file provides the template for the form, showing how to display errors for
117119
@if (passwordForm.confirmPassword().errors().length > 0) {
118120
<div class="errors">
119121
@for (error of passwordForm.confirmPassword().errors()) {
120-
@if (error.mismatch) { <p>Passwords do not match.</p> }
122+
@switch (error.kind) {
123+
@case ('mismatch') { <p>Passwords do not match.</p> }
124+
}
121125
}
122126
</div>
123127
}
@@ -129,8 +133,8 @@ This file provides the template for the form, showing how to display errors for
129133

130134
## Usage Notes
131135

132-
- The submit button is disabled based on the form's `valid` signal (`[disabled]="!passwordForm().valid()"`).
133-
- The `handleSubmit` method checks `this.passwordForm().valid()` as a safeguard before emitting the data.
136+
- The native `(submit)` event on the `<form>` element is bound to the `handleSubmit` method. It's important to call `$event.preventDefault()` to prevent a full page reload.
137+
- The `handleSubmit` method uses the `submit()` helper to manage the submission process.
134138

135139
## How to Use This Example
136140

0 commit comments

Comments
 (0)