Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0c84e42

Browse files
committed
feat(@angular/cli): add initial set of signal form examples
This commit introduces a comprehensive set of 18 examples for the new signal-based forms functionality in Angular. These examples are designed to be used with the `find_examples` tool (MCP) to provide developers with actionable, real-world code snippets covering a wide range of use cases. The `find_examples` tool is currently experimental and must be enabled using the `-E find_examples` option when running the `ng mcp` command. The examples cover the following key concepts: - Core concepts and basic setup - Built-in and cross-field validation - Dynamic and conditional form structures - Custom form controls and reusable validators - Asynchronous operations and third-party integrations (Zod) - Best practices for testing, accessibility, and styling
1 parent 1529595 commit 0c84e42

18 files changed

+3099
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
title: Basic Signal Form
3+
summary: Creates a reactive link between a WritableSignal data model and native HTML inputs using the `form()` function and `[control]` directive.
4+
keywords:
5+
- signal forms
6+
- form
7+
- control
8+
- basic example
9+
- submit
10+
required_packages:
11+
- '@angular/forms'
12+
related_concepts:
13+
- 'signals'
14+
---
15+
16+
## Purpose
17+
18+
The purpose of this pattern is to create a reactive, two-way link between a component's data model and its template. The problem it solves is keeping the state of the UI (the form inputs) perfectly synchronized with the application's state (the signal model) without manual state management, ensuring data consistency at all times.
19+
20+
## When to Use
21+
22+
Use this pattern as the starting point for any new form in an Angular application. It is the modern, preferred way to handle forms, superseding the traditional `FormsModule` (`NgModel`) and `ReactiveFormsModule` (`FormGroup`, `FormControl`) for most use cases by offering better type safety and a more reactive, signal-based architecture.
23+
24+
## Key Concepts
25+
26+
- **`form()`:** A function that creates a `Field` representing the form, taking a `WritableSignal` as the data model.
27+
- **`[control]` directive:** Binds a `Field` from your component to a native HTML input element, creating a two-way data binding.
28+
- **`submit()`:** An async helper function that manages the form's submission state and should be called from the submit event handler.
29+
30+
## Example Files
31+
32+
This example consists of a standalone component that defines and manages a user profile form.
33+
34+
### user-profile.component.ts
35+
36+
This file defines the component's logic, including the signal-based data model and the form structure.
37+
38+
```typescript
39+
import { Component, signal, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
40+
import { form, submit } from '@angular/forms/signals';
41+
import { JsonPipe } from '@angular/common';
42+
43+
export interface UserProfile {
44+
firstName: string;
45+
lastName: string;
46+
email: string;
47+
}
48+
49+
@Component({
50+
selector: 'app-user-profile',
51+
standalone: true,
52+
imports: [JsonPipe],
53+
templateUrl: './user-profile.component.html',
54+
changeDetection: ChangeDetectionStrategy.OnPush,
55+
})
56+
export class UserProfileComponent {
57+
@Output() submitted = new EventEmitter<UserProfile>();
58+
59+
profileModel = signal<UserProfile>({
60+
firstName: '',
61+
lastName: '',
62+
email: '',
63+
});
64+
65+
profileForm = form(this.profileModel);
66+
67+
async handleSubmit() {
68+
// For this basic example, we assume the form is always valid for submission.
69+
// See the validation example for how to handle invalid forms.
70+
await submit(this.profileForm, async () => {
71+
this.submitted.emit(this.profileForm().value());
72+
});
73+
}
74+
}
75+
```
76+
77+
### user-profile.component.html
78+
79+
The template is wrapped in a `<form>` tag with a `(submit)` event and a submit button.
80+
81+
```html
82+
<form (submit)="handleSubmit(); $event.preventDefault()">
83+
<div>
84+
<label>
85+
First Name:
86+
<input type="text" [control]="profileForm.firstName" />
87+
</label>
88+
</div>
89+
<div>
90+
<label>
91+
Last Name:
92+
<input type="text" [control]="profileForm.lastName" />
93+
</label>
94+
</div>
95+
<div>
96+
<label>
97+
Email:
98+
<input type="email" [control]="profileForm.email" />
99+
</label>
100+
</div>
101+
102+
<button type="submit">Submit</button>
103+
</form>
104+
105+
<h3>Live Form Value:</h3>
106+
<pre>{{ profileForm().value() | json }}</pre>
107+
```
108+
109+
## Usage Notes
110+
111+
- The `[control]` directive automatically handles the two-way data binding between the input element and the corresponding `Field` in the `profileForm`.
112+
- The `profileForm()` signal gives you access to the `FieldState`, which includes the form's `value` as a signal.
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.
115+
116+
## How to Use This Example
117+
118+
The parent component imports the `UserProfile` interface and listens for the `(submitted)` event to receive the strongly-typed form data.
119+
120+
```typescript
121+
// in app.component.ts
122+
import { Component } from '@angular/core';
123+
import { JsonPipe } from '@angular/common';
124+
import { UserProfileComponent, UserProfile } from './user-profile.component';
125+
126+
@Component({
127+
selector: 'app-root',
128+
standalone: true,
129+
imports: [UserProfileComponent, JsonPipe],
130+
template: `
131+
<h1>User Profile</h1>
132+
<app-user-profile (submitted)="onProfileSubmit($event)"></app-user-profile>
133+
134+
@if (submittedData) {
135+
<h2>Submitted Data:</h2>
136+
<pre>{{ submittedData | json }}</pre>
137+
}
138+
`,
139+
})
140+
export class AppComponent {
141+
submittedData: UserProfile | null = null;
142+
143+
onProfileSubmit(data: UserProfile) {
144+
this.submittedData = data;
145+
console.log('Profile data submitted:', data);
146+
}
147+
}
148+
```
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
---
2+
title: Signal Form with Built-in Validators
3+
summary: Demonstrates how to use the suite of built-in validators like `required`, `minLength`, `email`, and `pattern` within a signal form schema.
4+
keywords:
5+
- signal forms
6+
- validation
7+
- built-in validators
8+
- required
9+
- minLength
10+
- maxLength
11+
- min
12+
- max
13+
- email
14+
- pattern
15+
- schema
16+
required_packages:
17+
- '@angular/forms'
18+
related_concepts:
19+
- 'signals'
20+
---
21+
22+
## Purpose
23+
24+
The purpose of this pattern is to apply common, standard validation rules to form fields with minimal boilerplate. It solves the problem of repeatedly implementing frequent checks (e.g., for required fields, email formats, or length constraints) by providing a pre-built, configurable, and optimized set of validators.
25+
26+
## When to Use
27+
28+
Use this pattern as the primary method for applying common validation logic in your signal forms. These built-in functions should be your first choice before writing custom validators, as they cover the vast majority of real-world validation scenarios. They are the modern, signal-based equivalent of the static methods on the `Validators` class from `@angular/forms`.
29+
30+
## Key Concepts
31+
32+
- **`required()`:** A built-in validator that ensures a field has a value.
33+
- **`minLength(number)`:** A built-in validator that ensures a string's length is at least the specified minimum.
34+
- **`email()`:** A built-in validator that checks if a string is in a valid email format.
35+
- **`submit()`:** An async helper function that manages the form's submission state and should be called from the submit event handler.
36+
37+
## Example Files
38+
39+
This example consists of a standalone component that defines and manages a user settings form with built-in validators.
40+
41+
### user-settings.component.ts
42+
43+
This file defines the component's logic, including the data model and a validation schema that uses multiple built-in validators.
44+
45+
```typescript
46+
import { Component, signal, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
47+
import { form, schema, submit } from '@angular/forms/signals';
48+
import { required, minLength, maxLength, min, max, email, pattern } from '@angular/forms/signals';
49+
import { JsonPipe } from '@angular/common';
50+
51+
export interface UserSettings {
52+
username: string;
53+
age: number;
54+
email: string;
55+
website: string;
56+
}
57+
58+
const settingsSchema = schema<UserSettings>((form) => {
59+
required(form.username);
60+
minLength(form.username, 3);
61+
maxLength(form.username, 20);
62+
63+
required(form.age);
64+
min(form.age, 18);
65+
max(form.age, 100);
66+
67+
required(form.email);
68+
email(form.email);
69+
70+
pattern(form.website, /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/);
71+
});
72+
73+
@Component({
74+
selector: 'app-user-settings',
75+
standalone: true,
76+
imports: [JsonPipe],
77+
templateUrl: './user-settings.component.html',
78+
changeDetection: ChangeDetectionStrategy.OnPush,
79+
})
80+
export class UserSettingsComponent {
81+
@Output() submitted = new EventEmitter<UserSettings>();
82+
83+
settingsModel = signal<UserSettings>({
84+
username: '',
85+
age: 18,
86+
email: '',
87+
website: '',
88+
});
89+
90+
settingsForm = form(this.settingsModel, settingsSchema);
91+
92+
async handleSubmit() {
93+
await submit(this.settingsForm, async () => {
94+
this.submitted.emit(this.settingsForm().value());
95+
});
96+
}
97+
}
98+
```
99+
100+
### user-settings.component.html
101+
102+
This file provides the template for the form, displaying specific error messages for each potential validation failure.
103+
104+
```html
105+
<form (submit)="handleSubmit(); $event.preventDefault()">
106+
<div>
107+
<label>Username:</label>
108+
<input type="text" [control]="settingsForm.username" />
109+
@if (settingsForm.username().errors().length > 0) {
110+
<div class="errors">
111+
@for (error of settingsForm.username().errors()) { @switch (error.kind) { @case ('required') {
112+
<p>Username is required.</p>
113+
} @case ('minLength') {
114+
<p>Username must be at least 3 characters.</p>
115+
} @case ('maxLength') {
116+
<p>Username cannot exceed 20 characters.</p>
117+
} } }
118+
</div>
119+
}
120+
</div>
121+
122+
<div>
123+
<label>Age:</label>
124+
<input type="number" [control]="settingsForm.age" />
125+
@if (settingsForm.age().errors().length > 0) {
126+
<div class="errors">
127+
@for (error of settingsForm.age().errors()) { @switch (error.kind) { @case ('required') {
128+
<p>Age is required.</p>
129+
} @case ('min') {
130+
<p>You must be at least 18 years old.</p>
131+
} @case ('max') {
132+
<p>Age cannot be more than 100.</p>
133+
} } }
134+
</div>
135+
}
136+
</div>
137+
138+
<div>
139+
<label>Email:</label>
140+
<input type="email" [control]="settingsForm.email" />
141+
@if (settingsForm.email().errors().length > 0) {
142+
<div class="errors">
143+
@for (error of settingsForm.email().errors()) { @switch (error.kind) { @case ('required') {
144+
<p>Email is required.</p>
145+
} @case ('email') {
146+
<p>Please enter a valid email address.</p>
147+
} } }
148+
</div>
149+
}
150+
</div>
151+
152+
<div>
153+
<label>Website:</label>
154+
<input type="url" [control]="settingsForm.website" />
155+
@if (settingsForm.website().errors().length > 0) {
156+
<div class="errors">
157+
@for (error of settingsForm.website().errors()) { @switch (error.kind) { @case ('pattern') {
158+
<p>Please enter a valid URL.</p>
159+
} } }
160+
</div>
161+
}
162+
</div>
163+
164+
<button type="submit" [disabled]="!settingsForm().valid()">Save Settings</button>
165+
</form>
166+
```
167+
168+
## Usage Notes
169+
170+
- All built-in validators are imported directly from `@angular/forms`.
171+
- 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.
172+
- The `handleSubmit` method uses the `submit()` helper to manage the submission process.
173+
174+
## How to Use This Example
175+
176+
The parent component listens for the `(submitted)` event to receive the validated form data.
177+
178+
```typescript
179+
// in app.component.ts
180+
import { Component } from '@angular/core';
181+
import { JsonPipe } from '@angular/common';
182+
import { UserSettingsComponent, UserSettings } from './user-settings.component';
183+
184+
@Component({
185+
selector: 'app-root',
186+
standalone: true,
187+
imports: [UserSettingsComponent, JsonPipe],
188+
template: `
189+
<h1>User Settings</h1>
190+
<app-user-settings (submitted)="onFormSubmit($event)"></app-user-settings>
191+
192+
@if (submittedData) {
193+
<h2>Submitted Data:</h2>
194+
<pre>{{ submittedData | json }}</pre>
195+
}
196+
`,
197+
})
198+
export class AppComponent {
199+
submittedData: UserSettings | null = null;
200+
201+
onFormSubmit(data: UserSettings) {
202+
this.submittedData = data;
203+
console.log('Settings data submitted:', data);
204+
}
205+
}
206+
```

0 commit comments

Comments
 (0)