|
| 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 | + imports: [JsonPipe], |
| 76 | + templateUrl: './user-settings.component.html', |
| 77 | + changeDetection: ChangeDetectionStrategy.OnPush, |
| 78 | +}) |
| 79 | +export class UserSettingsComponent { |
| 80 | + @Output() submitted = new EventEmitter<UserSettings>(); |
| 81 | + |
| 82 | + settingsModel = signal<UserSettings>({ |
| 83 | + username: '', |
| 84 | + age: 18, |
| 85 | + email: '', |
| 86 | + website: '', |
| 87 | + }); |
| 88 | + |
| 89 | + settingsForm = form(this.settingsModel, settingsSchema); |
| 90 | + |
| 91 | + async handleSubmit() { |
| 92 | + await submit(this.settingsForm, async () => { |
| 93 | + this.submitted.emit(this.settingsForm().value()); |
| 94 | + }); |
| 95 | + } |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +### user-settings.component.html |
| 100 | + |
| 101 | +This file provides the template for the form, displaying specific error messages for each potential validation failure. |
| 102 | + |
| 103 | +```html |
| 104 | +<form (submit)="handleSubmit(); $event.preventDefault()"> |
| 105 | + <div> |
| 106 | + <label>Username:</label> |
| 107 | + <input type="text" [control]="settingsForm.username" /> |
| 108 | + @if (settingsForm.username().errors().length > 0) { |
| 109 | + <div class="errors"> |
| 110 | + @for (error of settingsForm.username().errors()) { @switch (error.kind) { @case ('required') { |
| 111 | + <p>Username is required.</p> |
| 112 | + } @case ('minLength') { |
| 113 | + <p>Username must be at least 3 characters.</p> |
| 114 | + } @case ('maxLength') { |
| 115 | + <p>Username cannot exceed 20 characters.</p> |
| 116 | + } } } |
| 117 | + </div> |
| 118 | + } |
| 119 | + </div> |
| 120 | + |
| 121 | + <div> |
| 122 | + <label>Age:</label> |
| 123 | + <input type="number" [control]="settingsForm.age" /> |
| 124 | + @if (settingsForm.age().errors().length > 0) { |
| 125 | + <div class="errors"> |
| 126 | + @for (error of settingsForm.age().errors()) { @switch (error.kind) { @case ('required') { |
| 127 | + <p>Age is required.</p> |
| 128 | + } @case ('min') { |
| 129 | + <p>You must be at least 18 years old.</p> |
| 130 | + } @case ('max') { |
| 131 | + <p>Age cannot be more than 100.</p> |
| 132 | + } } } |
| 133 | + </div> |
| 134 | + } |
| 135 | + </div> |
| 136 | + |
| 137 | + <div> |
| 138 | + <label>Email:</label> |
| 139 | + <input type="email" [control]="settingsForm.email" /> |
| 140 | + @if (settingsForm.email().errors().length > 0) { |
| 141 | + <div class="errors"> |
| 142 | + @for (error of settingsForm.email().errors()) { @switch (error.kind) { @case ('required') { |
| 143 | + <p>Email is required.</p> |
| 144 | + } @case ('email') { |
| 145 | + <p>Please enter a valid email address.</p> |
| 146 | + } } } |
| 147 | + </div> |
| 148 | + } |
| 149 | + </div> |
| 150 | + |
| 151 | + <div> |
| 152 | + <label>Website:</label> |
| 153 | + <input type="url" [control]="settingsForm.website" /> |
| 154 | + @if (settingsForm.website().errors().length > 0) { |
| 155 | + <div class="errors"> |
| 156 | + @for (error of settingsForm.website().errors()) { @switch (error.kind) { @case ('pattern') { |
| 157 | + <p>Please enter a valid URL.</p> |
| 158 | + } } } |
| 159 | + </div> |
| 160 | + } |
| 161 | + </div> |
| 162 | + |
| 163 | + <button type="submit" [disabled]="!settingsForm().valid()">Save Settings</button> |
| 164 | +</form> |
| 165 | +``` |
| 166 | + |
| 167 | +## Usage Notes |
| 168 | + |
| 169 | +- All built-in validators are imported directly from `@angular/forms`. |
| 170 | +- 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. |
| 171 | +- The `handleSubmit` method uses the `submit()` helper to manage the submission process. |
| 172 | + |
| 173 | +## How to Use This Example |
| 174 | + |
| 175 | +The parent component listens for the `(submitted)` event to receive the validated form data. |
| 176 | + |
| 177 | +```typescript |
| 178 | +// in app.component.ts |
| 179 | +import { Component } from '@angular/core'; |
| 180 | +import { JsonPipe } from '@angular/common'; |
| 181 | +import { UserSettingsComponent, UserSettings } from './user-settings.component'; |
| 182 | + |
| 183 | +@Component({ |
| 184 | + selector: 'app-root', |
| 185 | + imports: [UserSettingsComponent, JsonPipe], |
| 186 | + template: ` |
| 187 | + <h1>User Settings</h1> |
| 188 | + <app-user-settings (submitted)="onFormSubmit($event)"></app-user-settings> |
| 189 | +
|
| 190 | + @if (submittedData) { |
| 191 | + <h2>Submitted Data:</h2> |
| 192 | + <pre>{{ submittedData | json }}</pre> |
| 193 | + } |
| 194 | + `, |
| 195 | +}) |
| 196 | +export class AppComponent { |
| 197 | + submittedData: UserSettings | null = null; |
| 198 | + |
| 199 | + onFormSubmit(data: UserSettings) { |
| 200 | + this.submittedData = data; |
| 201 | + console.log('Settings data submitted:', data); |
| 202 | + } |
| 203 | +} |
| 204 | +``` |
0 commit comments