Валидация
Формам нужна валидация, чтобы гарантировать, что пользователи предоставляют корректные и полные данные перед отправкой. Без валидации вам пришлось бы обрабатывать проблемы с качеством данных на сервере, ухудшать пользовательский опыт из-за неясных сообщений об ошибках и вручную проверять каждое ограничение.
Signal Forms предоставляет подход к валидации на основе схем. Правила валидации привязываются к полям с помощью функции схемы, автоматически запускаются при изменении значений, а ошибки доступны через сигналы состояния поля. Это позволяет выполнять реактивную валидацию, которая обновляется по мере взаимодействия пользователей с формой.
Основы валидации
Валидация в Signal Forms определяется с помощью функции схемы, передаваемой вторым аргументом в form().
Функция схемы
Функция схемы получает объект SchemaPathTree, который позволяет задавать правила валидации:
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {Component, signal} from '@angular/core';
import {email, form, FormField, required, submit} from '@angular/forms/signals';
interface LoginData {
email: string;
password: string;
}
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [FormField],
})
export class App {
loginModel = signal<LoginData>({
email: '',
password: '',
});
loginForm = form(this.loginModel, (schemaPath) => {
required(schemaPath.email, {message: 'Email is required'});
email(schemaPath.email, {message: 'Enter a valid email address'});
required(schemaPath.password, {message: 'Password is required'});
});
onSubmit(event: Event) {
event.preventDefault();
submit(this.loginForm, {
action: async () => {
const credentials = this.loginModel();
// In a real app, this would be async:
// await this.authService.login(credentials);
console.log('Logging in with:', credentials);
},
});
}
}
Функция схемы запускается один раз во время инициализации формы. Правила валидации привязываются к полям с помощью параметра пути схемы (например, schemaPath.email, schemaPath.password), а валидация автоматически выполняется при каждом изменении значений полей.
ПРИМЕЧАНИЕ: Параметр обратного вызова схемы (schemaPath в этих примерах) — это объект SchemaPathTree, предоставляющий пути ко всем полям формы. Вы можете назвать этот параметр как угодно.
Как работает валидация
Валидация в Signal Forms следует такому шаблону:
- Определение правил валидации в схеме — привяжите правила валидации к полям в функции схемы
- Автоматическое выполнение — правила валидации запускаются при изменении значений полей
- Распространение ошибок — ошибки валидации доступны через сигналы состояния поля
- Реактивные обновления — интерфейс автоматически обновляется при изменении состояния валидации
Для интерактивных полей валидация выполняется при каждом изменении значения. Для скрытых и отключённых полей валидация не запускается: правила пропускаются, пока поле снова не станет интерактивным.
Порядок выполнения валидации
Правила валидации выполняются в следующем порядке:
- Синхронная валидация — все правила синхронной валидации запускаются при изменении значения
- Асинхронная валидация — правила асинхронной валидации запускаются только после успешного прохождения всех правил синхронной валидации
-
Обновление состояния поля — обновляются сигналы
valid(),invalid(),errors()иpending()
Правила синхронной валидации (например, required(), email()) выполняются сразу. Правила асинхронной валидации (например, validateHttp()) могут выполняться некоторое время и устанавливают сигнал pending() в значение true на время выполнения.
Все правила валидации запускаются при каждом изменении — после первой ошибки валидация не прекращается. Если для поля заданы правила валидации required() и email(), запускаются оба правила, и каждое из них может одновременно выдать ошибку.
Встроенные правила валидации
Signal Forms предоставляет правила валидации для распространённых сценариев. Все встроенные правила валидации принимают объект параметров для настройки сообщений об ошибках и условной логики.
required()
Правило валидации required() гарантирует, что поле содержит значение:
import {Component, signal} from '@angular/core';
import {form, FormField, required} from '@angular/forms/signals';
@Component({
selector: 'app-registration',
imports: [FormField],
template: `
<form novalidate>
<label>
Username
<input [formField]="registrationForm.username" />
</label>
<label>
Email
<input type="email" [formField]="registrationForm.email" />
</label>
<button type="submit">Register</button>
</form>
`,
})
export class RegistrationComponent {
registrationModel = signal({
username: '',
email: '',
});
registrationForm = form(this.registrationModel, (schemaPath) => {
required(schemaPath.username, {message: 'Username is required'});
required(schemaPath.email, {message: 'Email is required'});
});
}
Поле считается «пустым» в следующих случаях:
| Условие | Пример |
|---|---|
Значение — null
|
null, |
| Значение — пустая строка | '' |
Для условного указания обязательности используйте параметр when:
registrationForm = form(this.registrationModel, (schemaPath) => {
required(schemaPath.promoCode, {
message: 'Promo code is required for discounts',
when: ({valueOf}) => valueOf(schemaPath.applyDiscount),
});
});
Правило валидации запускается, только если функция when возвращает true.
ПРИМЕЧАНИЕ: required вернёт true для пустого массива. Для валидации массивов используйте minLength().
email()
Правило валидации email() проверяет корректность формата адреса электронной почты:
import {Component, signal} from '@angular/core';
import {form, FormField, email} from '@angular/forms/signals';
@Component({
selector: 'app-contact',
imports: [FormField],
template: `
<form novalidate>
<label>
Your Email
<input type="email" [formField]="contactForm.email" />
</label>
</form>
`,
})
export class ContactComponent {
contactModel = signal({email: ''});
contactForm = form(this.contactModel, (schemaPath) => {
email(schemaPath.email, {message: 'Please enter a valid email address'});
});
}
Правило валидации email() использует стандартное регулярное выражение для проверки формата электронной почты. Оно принимает адреса, например user@example.com, но отклоняет некорректные адреса, например user@ или @example.com.
min() и max()
Правила валидации min() и max() работают с числовыми значениями:
import {Component, signal} from '@angular/core';
import {form, FormField, min, max} from '@angular/forms/signals';
@Component({
selector: 'app-age-form',
imports: [FormField],
template: `
<form novalidate>
<label>
Age
<input type="number" [formField]="ageForm.age" />
</label>
<label>
Rating (1-5)
<input type="number" [formField]="ageForm.rating" />
</label>
</form>
`,
})
export class AgeFormComponent {
ageModel = signal({
age: 0,
rating: 0,
});
ageForm = form(this.ageModel, (schemaPath) => {
min(schemaPath.age, 18, {message: 'You must be at least 18 years old'});
max(schemaPath.age, 120, {message: 'Please enter a valid age'});
min(schemaPath.rating, 1, {message: 'Rating must be at least 1'});
max(schemaPath.rating, 5, {message: 'Rating cannot exceed 5'});
});
}
Для динамических ограничений можно использовать вычисляемые значения:
ageForm = form(this.ageModel, (schemaPath) => {
min(schemaPath.participants, () => this.minimumRequired(), {
message: 'Not enough participants',
});
}); minLength() и maxLength()
Правила валидации minLength() и maxLength() работают со строками и массивами:
import {Component, signal} from '@angular/core';
import {form, FormField, minLength, maxLength} from '@angular/forms/signals';
@Component({
selector: 'app-password-form',
imports: [FormField],
template: `
<form novalidate>
<label>
Password
<input type="password" [formField]="passwordForm.password" />
</label>
<label>
Bio
<textarea [formField]="passwordForm.bio"></textarea>
</label>
</form>
`,
})
export class PasswordFormComponent {
passwordModel = signal({
password: '',
bio: '',
});
passwordForm = form(this.passwordModel, (schemaPath) => {
minLength(schemaPath.password, 8, {message: 'Password must be at least 8 characters'});
maxLength(schemaPath.password, 100, {message: 'Password is too long'});
maxLength(schemaPath.bio, 500, {message: 'Bio cannot exceed 500 characters'});
});
}
Для строк «длина» означает количество символов. Для массивов «длина» означает количество элементов.
pattern()
Правило валидации pattern() проверяет значение по регулярному выражению:
import {Component, signal} from '@angular/core';
import {form, FormField, pattern} from '@angular/forms/signals';
@Component({
selector: 'app-phone-form',
imports: [FormField],
template: `
<form novalidate>
<label>
Phone Number
<input [formField]="phoneForm.phone" placeholder="555-123-4567" />
</label>
<label>
Postal Code
<input [formField]="phoneForm.postalCode" placeholder="12345" />
</label>
</form>
`,
})
export class PhoneFormComponent {
phoneModel = signal({
phone: '',
postalCode: '',
});
phoneForm = form(this.phoneModel, (schemaPath) => {
pattern(schemaPath.phone, /^\d{3}-\d{3}-\d{4}$/, {
message: 'Phone must be in format: 555-123-4567',
});
pattern(schemaPath.postalCode, /^\d{5}$/, {
message: 'Postal code must be 5 digits',
});
});
}
Распространённые шаблоны:
| Тип шаблона | Регулярное выражение | Пример |
|---|---|---|
| Телефон | /^\d{3}-\d{3}-\d{4}$/ | 555-123-4567 |
| Почтовый индекс (США) | /^\d{5}$/ | 12345 |
| Буквенно-цифровой | /^[a-zA-Z0-9]+$/ | abc123 |
| Допустимый для URL | /^[a-zA-Z0-9_-]+$/ | my-url_123 |
Валидация элементов массива
Формы могут содержать массивы вложенных объектов (например, список товаров в заказе). Чтобы применить правила валидации к каждому элементу массива, используйте applyEach() внутри функции схемы. applyEach() перебирает путь массива и предоставляет путь для каждого элемента, к которому можно применить валидаторы так же, как и к полям верхнего уровня.
import {Component, signal} from '@angular/core';
import {applyEach, FormField, form, min, required, SchemaPathTree} from '@angular/forms/signals';
type Item = {name: string; quantity: number};
interface Order {
title: string;
description: string;
items: Item[];
}
function ItemSchema(item: SchemaPathTree<Item>) {
required(item.name, {message: 'Item name is required'});
min(item.quantity, 1, {message: 'Quantity must be at least 1'});
}
@Component(/* ... */)
export class OrderComponent {
orderModel = signal<Order>({
title: '',
description: '',
items: [{name: '', quantity: 0}],
});
orderForm = form(this.orderModel, (schemaPath) => {
required(schemaPath.title);
required(schemaPath.description);
applyEach(schemaPath.items, ItemSchema);
});
} Ошибки валидации
Если правила валидации не выполняются, они создают объекты ошибок с описанием проблемы. Понимание структуры ошибок поможет вам предоставлять пользователям понятные пояснения.
Структура ошибки
Каждый объект ошибки валидации содержит следующие свойства:
| Свойство | Описание |
|---|---|
kind | Правило валидации, которое не было выполнено (например, "required", "email", "minLength") |
message | Необязательное понятное пользователю сообщение об ошибке |
Встроенные правила валидации автоматически задают свойство kind. Свойство message является необязательным — вы можете задать собственные сообщения в параметрах правила валидации.
Пользовательские сообщения об ошибках
Все встроенные правила валидации принимают параметр message для пользовательского текста ошибки:
import {Component, signal} from '@angular/core';
import {form, FormField, required, minLength} from '@angular/forms/signals';
@Component({
selector: 'app-signup',
imports: [FormField],
template: `
<form novalidate>
<label>
Username
<input [formField]="signupForm.username" />
</label>
<label>
Password
<input type="password" [formField]="signupForm.password" />
</label>
</form>
`,
})
export class SignupComponent {
signupModel = signal({
username: '',
password: '',
});
signupForm = form(this.signupModel, (schemaPath) => {
required(schemaPath.username, {
message: 'Please choose a username',
});
required(schemaPath.password, {
message: 'Password cannot be empty',
});
minLength(schemaPath.password, 12, {
message: 'Password must be at least 12 characters for security',
});
});
}
Пользовательские сообщения должны быть понятными и конкретными, а также объяснять пользователям, как исправить проблему. Вместо «Недопустимый ввод» используйте «Для безопасности пароль должен содержать не менее 12 символов».
Несколько ошибок для одного поля
Если для поля заданы несколько правил валидации, каждое из них выполняется независимо и может выдать ошибку:
signupForm = form(this.signupModel, (schemaPath) => {
required(schemaPath.email, {message: 'Email is required'});
email(schemaPath.email, {message: 'Enter a valid email address'});
minLength(schemaPath.email, 5, {message: 'Email is too short'});
});
Если поле электронной почты пустое, отображается только ошибка required(). Если пользователь вводит «a@b», появляются ошибки email() и minLength(). Выполняются все правила валидации — проверка не останавливается после первой ошибки.
СОВЕТ: Используйте в шаблонах шаблон touched() && invalid(), чтобы ошибки не отображались до того, как пользователь взаимодействовал с полем. Подробные рекомендации по отображению ошибок валидации см. в руководстве Управление состоянием поля.
Пользовательские правила валидации
Встроенные правила валидации подходят для распространённых случаев, но часто требуется пользовательская логика валидации для бизнес-правил, сложных форматов или специфичных для предметной области ограничений.
Использование validate()
Функция validate() создаёт пользовательские правила валидации. Она получает функцию-валидатор, которая обращается к контексту поля и возвращает:
| Возвращаемое значение | Значение |
|---|---|
| Объект ошибки | Значение недопустимо |
null или undefined
| Значение допустимо |
import {Component, signal} from '@angular/core';
import {form, FormField, validate} from '@angular/forms/signals';
@Component({
selector: 'app-url-form',
imports: [FormField],
template: `
<form novalidate>
<label>
Website URL
<input [formField]="urlForm.website" />
</label>
</form>
`,
})
export class UrlFormComponent {
urlModel = signal({website: ''});
urlForm = form(this.urlModel, (schemaPath) => {
validate(schemaPath.website, ({value}) => {
if (!value().startsWith('https://')) {
return {
kind: 'https',
message: 'URL must start with https://',
};
}
return null;
});
});
}
Функция-валидатор получает объект FieldContext со следующими свойствами:
| Свойство | Тип | Описание |
|---|---|---|
value | Сигнал | Сигнал, содержащий текущее значение поля |
state | FieldState | Ссылка на состояние поля |
field | FieldTree | Ссылка на дерево полей |
valueOf() | Метод | Получает значение другого поля по пути |
stateOf() | Метод | Получает состояние другого поля по пути |
fieldTreeOf() | Метод | Получает дерево полей другого поля по пути |
pathKeys | Сигнал | Ключи пути от корня до текущего поля |
ПРИМЕЧАНИЕ: У дочерних полей также есть сигнал key, а у полей — элементов массива есть сигналы key и index.
Если валидация не пройдена, верните объект ошибки с kind и message. Если валидация пройдена, верните null или undefined.
Использование validateTree()
Функция validateTree() создаёт пользовательские правила валидации, которые могут применяться к нескольким полям или реализовывать сложную логику валидации для всего поддерева.
import {Component, model} from '@angular/core';
import {form, FormField, validateTree} from '@angular/forms/signals';
interface User {
firstName: string;
lastName: string;
}
@Component({
/* ... */
})
export class UserFormComponent {
readonly userModel = model<User>({
firstName: '',
lastName: '',
});
userForm = form(this.userModel, (path) => {
validateTree(path, (ctx) => {
if (ctx.valueOf(path.firstName).length < 5) {
return {
kind: 'minLength5',
message: 'First name must be at least 5 characters',
fieldTree: ctx.fieldTree.lastName,
};
}
return null;
});
});
}
Функция-валидатор validateTree() получает тот же объект FieldContext, что и validate().
Повторно используемые правила валидации
Создавайте повторно используемые функции правил валидации, оборачивая validate():
function url(path: SchemaPath<string>, options?: {message?: string}) {
validate(path, ({value}) => {
try {
new URL(value());
return null;
} catch {
return {
kind: 'url',
message: options?.message || 'Enter a valid URL',
};
}
});
}
function phoneNumber(path: SchemaPath<string>, options?: {message?: string}) {
validate(path, ({value}) => {
const phoneRegex = /^\d{3}-\d{3}-\d{4}$/;
if (!phoneRegex.test(value())) {
return {
kind: 'phoneNumber',
message: options?.message || 'Phone must be in format: 555-123-4567',
};
}
return null;
});
}
Пользовательские правила валидации можно использовать так же, как встроенные:
urlForm = form(this.urlModel, (schemaPath) => {
url(schemaPath.website, {message: 'Please enter a valid website URL'});
phoneNumber(schemaPath.phone);
}); Межполевая валидация
Межполевая валидация сравнивает значения нескольких полей или проверяет связь между ними.
Распространённый сценарий межполевой валидации — подтверждение пароля:
import {Component, signal} from '@angular/core';
import {form, FormField, required, minLength, validate} from '@angular/forms/signals';
@Component({
selector: 'app-password-change',
imports: [FormField],
template: `
<form novalidate>
<label>
New Password
<input type="password" [formField]="passwordForm.password" />
</label>
<label>
Confirm Password
<input type="password" [formField]="passwordForm.confirmPassword" />
</label>
<button type="submit">Change Password</button>
</form>
`,
})
export class PasswordChangeComponent {
passwordModel = signal({
password: '',
confirmPassword: '',
});
passwordForm = form(this.passwordModel, (schemaPath) => {
required(schemaPath.password, {message: 'Password is required'});
minLength(schemaPath.password, 8, {message: 'Password must be at least 8 characters'});
required(schemaPath.confirmPassword, {message: 'Please confirm your password'});
validate(schemaPath.confirmPassword, ({value, valueOf}) => {
const confirmPassword = value();
const password = valueOf(schemaPath.password);
if (confirmPassword !== password) {
return {
kind: 'passwordMismatch',
message: 'Passwords do not match',
};
}
return null;
});
});
}
Правило валидации для подтверждения получает значение поля пароля с помощью valueOf(schemaPath.password) и сравнивает его со значением подтверждения. Это правило валидации работает реактивно: если изменится пароль или его подтверждение, валидация автоматически запустится повторно.
Условная валидация
Иногда правило валидации должно применяться только при определённых условиях, например проверять адрес доставки, только если заказ отправляется за границу, или применять разные наборы правил к каждому варианту поля с объединённым типом.
Поскольку правила валидации находятся в функции схемы, для их условного применения используйте те же структурные функции, что и для составления схем:
- Используйте
applyWhen(), чтобы активировать группу правил на основе реактивного состояния формы, включая значения других полей. - Используйте
applyWhenValue(), чтобы применять правила на основе собственного значения поля. Если предикат является защитой типа, типы правил будут соответствовать суженному типу значения. Поэтому этот способ рекомендуется для валидации дискриминируемых объединений и других вариантов типов.
Полные примеры, в том числе повторно используемые схемы и дискриминируемые объединения, см. в руководстве Схемы и композиция схем.
Асинхронная валидация
Асинхронная валидация используется, когда для проверки требуются внешние источники данных, например при проверке доступности имени пользователя на сервере или проверке через API.
Использование validateHttp()
Функция validateHttp() выполняет валидацию с помощью HTTP:
import {Component, signal} from '@angular/core';
import {form, FormField, required, validateHttp} from '@angular/forms/signals';
@Component({
selector: 'app-username-form',|
imports: [FormField],
template: `
<form novalidate>
<label>
Username
<input [formField]="usernameForm.username" />
@if (usernameForm.username().pending()) {
<span class="checking">Checking availability...</span>
}
</label>
</form>
`,
})
export class UsernameFormComponent {
usernameModel = signal({username: ''});
usernameForm = form(this.usernameModel, (schemaPath) => {
required(schemaPath.username, {message: 'Username is required'});
validateHttp(schemaPath.username, {
request: ({value}) => `/api/check-username?username=${value()}`,
onSuccess: (response: any) => {
if (response.taken) {
return {
kind: 'usernameTaken',
message: 'Username is already taken',
};
}
return null;
},
onError: (error) => ({
kind: 'networkError',
message: 'Could not verify username availability',
}),
});
});
}
Правило валидации validateHttp():
- Вызывает URL или отправляет запрос, возвращённый функцией
request - Преобразует успешный ответ в ошибку валидации или
nullс помощьюonSuccess - Обрабатывает сбои запроса (сетевые ошибки, ошибки HTTP) с помощью
onError - Устанавливает
pending()в значениеtrue, пока выполняется запрос - Запускается только после успешного прохождения всех правил синхронной валидации
Состояние ожидания
Во время выполнения асинхронной валидации сигнал pending() поля возвращает true. Используйте его для отображения индикаторов загрузки:
@if (form.username().pending()) {
<span class="spinner">Checking...</span>
}
Пока валидация ожидает выполнения, сигнал valid() возвращает false, даже если ошибок ещё нет. Сигнал invalid() возвращает true, только если есть ошибки.
Интеграция с библиотеками валидации схем
Signal Forms имеет встроенную поддержку библиотек, соответствующих стандарту Standard Schema, таких как Zod и Valibot. Интеграция реализована с помощью функции validateStandardSchema. Она позволяет использовать существующие схемы, сохраняя преимущества реактивной валидации Signal Forms.
import {form, validateStandardSchema} from '@angular/forms/signals';
import * as z from 'zod';
// Define your schema
const userSchema = z.object({
email: z.email(),
password: z.string().min(8),
});
// Use with Signal Forms
const userForm = form(signal({email: '', password: ''}), (schemaPath) => {
validateStandardSchema(schemaPath, userSchema);
}); Динамические схемы
Вместо статической схемы можно передать сигнал, чтобы схема валидации автоматически обновлялась при изменении своих зависимостей.
import {Component, computed, signal} from '@angular/core';
import {form, FormField, validateStandardSchema} from '@angular/forms/signals';
import z from 'zod';
@Component({
/* ... */
})
export class DynamicSchema {
model = signal({document: '', type: 'dni'});
// Schema reacts automatically to type changes
schema = computed(() =>
z.object({
document:
this.model().type === 'dni'
? z.string().length(8, 'DNI must be 8 digits')
: z.string().min(12, 'Passport must be at least 12 characters'),
}),
);
f = form(this.model, (p) => validateStandardSchema(p, () => this.schema()));
} Дальнейшие шаги
В этом руководстве описано создание и применение правил валидации. В связанных руководствах рассматриваются другие аспекты Signal Forms:
Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/forms/signals/validation