Модели форм
Модели форм — основа Signal Forms и единственный источник достоверных данных вашей формы. В этом руководстве рассматривается создание и обновление моделей форм, а также их проектирование с учётом удобства сопровождения.
ПРИМЕЧАНИЕ: Модели форм отличаются от сигнала model() в Angular, используемого для двусторонней привязки компонентов. Модель формы — это записываемый сигнал, хранящий данные формы, а model() создаёт входы и выходы для взаимодействия родительского и дочернего компонентов.
Какие задачи решают модели форм
Формы требуют управления данными, которые меняются со временем. Без чёткой структуры эти данные могут оказаться разбросанными по свойствам компонента, из-за чего становится трудно отслеживать изменения, проверять введённые значения или отправлять данные на сервер.
Модели форм решают эту проблему, централизуя данные формы в одном записываемом сигнале. Когда модель обновляется, форма автоматически отражает эти изменения. Когда пользователь взаимодействует с формой, модель обновляется соответствующим образом.
Создание моделей
Модель формы — это записываемый сигнал, создаваемый функцией signal() Angular. Сигнал содержит объект, представляющий структуру данных формы.
import {Component, signal} from '@angular/core';
import {form, FormField} from '@angular/forms/signals';
@Component({
selector: 'app-login',
imports: [FormField],
template: `
<input type="email" [formField]="loginForm.email" />
<input type="password" [formField]="loginForm.password" />
`,
})
export class LoginComponent {
loginModel = signal({
email: '',
password: '',
});
loginForm = form(this.loginModel);
}
Функция form() принимает сигнал модели и создаёт дерево полей — специальную структуру объектов, повторяющую форму модели. По дереву полей можно перемещаться (получать доступ к дочерним полям через точечную нотацию, например loginForm.email), а также вызывать его элементы (вызов поля как функции позволяет получить доступ к его состоянию).
Директива [formField] связывает каждый элемент ввода с соответствующим полем в дереве полей, обеспечивая автоматическую двустороннюю синхронизацию между пользовательским интерфейсом и моделью.
Поддерживаемые структуры моделей
Signal Forms создаёт дерево полей, обходя модель. Объекты и массивы, которые оно обходит (структурный уровень), должны быть обычными объектами и массивами JavaScript. Значения на листьях (позициях без вложенных полей) обычно являются примитивами (строками, числами, логическими значениями) или null. Встроенные элементы ввода date, month, time и week также принимают Date, а пользовательские элементы управления могут принимать значения любых типов, которые они поддерживают.
interface UserFormModel {
name: string;
birthday: Date | null;
preferences: {
theme: string;
notifications: boolean;
};
tags: string[];
}
const userModel = signal<UserFormModel>({
name: '',
birthday: null,
preferences: {
theme: 'dark',
notifications: true,
},
tags: [],
});
ВАЖНО: Экземпляры классов, Map и Set не поддерживаются на структурном уровне, несмотря на то что TypeScript их принимает. Signal Forms не проверяет структуру модели во время выполнения, поэтому платформа принимает эти значения без ошибки, а затем работает некорректно — по-разному в зависимости от структуры:
-
Экземпляры классов теряют свой прототип при первой записи, поскольку Signal Forms при обновлении неглубоко копирует родительские объекты. Методы, геттеры и проверки
instanceofпосле этого перестают работать. - Не допускающие расширения или замороженные объекты внутри массивов приводят к ошибке, когда Signal Forms присваивает им символ отслеживания для сохранения идентичности элементов при изменении порядка.
-
MapиSetсоздают пустые деревья полей, поскольку Signal Forms перечисляет дочерние элементы с помощьюObject.keys.
Если в приложении для моделирования предметной области используются классы, преобразуйте их в обычные объекты на границе формы. См. раздел Преобразование между моделью формы и моделью предметной области.
Использование типов TypeScript
TypeScript выводит типы из литералов объектов, но явное определение типов повышает качество кода и улучшает поддержку IntelliSense.
interface LoginData {
email: string;
password: string;
}
export class LoginComponent {
loginModel = signal<LoginData>({
email: '',
password: '',
});
loginForm = form(this.loginModel);
}
При явном задании типов дерево полей обеспечивает полную проверку типов. Тип поля loginForm.email определяется как FieldTree<string>, а попытка обратиться к несуществующему свойству приводит к ошибке компиляции.
// TypeScript knows this is FieldTree<string> const emailField = loginForm.email; // TypeScript error: Property 'username' does not exist const usernameField = loginForm.username;
Инициализация всех полей
В моделях форм следует задавать начальные значения для всех полей, которые нужно включить в дерево полей.
// Good: All fields initialized
const userModel = signal({
name: '',
email: '',
age: 0,
});
// Avoid: Missing initial value
const userModel = signal({
name: '',
email: '',
// age field is not defined - cannot access userForm.age
});
Для необязательных полей явно задайте пустое значение или null:
interface UserData {
name: string;
email: string;
phoneNumber: string | null;
}
const userModel = signal<UserData>({
name: '',
email: '',
phoneNumber: null,
});
ПОЛЕЗНО ЗНАТЬ: Встроенные текстовые элементы управления, такие как <input type=text> и <textarea>, не поддерживают null. Используйте '' для представления пустого значения.
Поля со значением undefined исключаются из дерева полей. Модель с {value: undefined} ведёт себя так же, как {}: при обращении к полю возвращается undefined, а не FieldTree.
Чтение значений модели
Получить доступ к значениям формы можно двумя способами: напрямую из сигнала модели или через отдельные поля. Каждый способ подходит для своих задач.
Чтение из модели
Обращайтесь к сигналу модели, когда нужны все данные формы, например при её отправке:
async onSubmit() {
const formData = this.loginModel();
console.log(formData.email, formData.password);
// Send to server
await this.authService.login(formData);
}
Сигнал модели возвращает весь объект данных, поэтому он идеально подходит для операций, работающих с полным состоянием формы.
Чтение состояния поля
Каждое поле в дереве полей является функцией. Вызов поля возвращает объект FieldState, содержащий реактивные сигналы значения поля, статуса проверки и состояния взаимодействия.
Обращайтесь к состоянию поля при работе с отдельными полями в шаблонах или реактивных вычислениях:
@Component({
template: `
<p>Current email: {{ loginForm.email().value() }}</p>
<p>Password length: {{ passwordLength() }}</p>
`,
})
export class LoginComponent {
loginModel = signal({email: '', password: ''});
loginForm = form(this.loginModel);
passwordLength = computed(() => {
return this.loginForm.password().value().length;
});
}
Состояние поля предоставляет реактивные сигналы для каждого значения поля, что удобно для отображения информации об отдельном поле или создания производного состояния.
СОВЕТ: Состояние поля содержит множество сигналов помимо value(), например сигналы состояния проверки (например, допустимое, недопустимое, ошибки), отслеживания взаимодействия (например, затронутое, изменённое) и видимости (например, скрытое, отключённое).
Программное обновление моделей форм
Замена моделей форм с помощью set()
Используйте set() для модели формы, чтобы заменить её значение целиком:
loadUserData() {
this.userModel.set({
name: 'Alice',
email: 'alice@example.com',
age: 30,
});
}
resetForm() {
this.userModel.set({
name: '',
email: '',
age: 0,
});
}
Этот способ удобен при загрузке данных из API или сбросе всей формы.
Прямое обновление отдельного поля с помощью set() или update()
Используйте set() для значений отдельных полей, чтобы напрямую обновить состояние поля:
clearEmail() {
this.userForm.email().value.set('');
}
incrementAge() {
this.userForm.age().value.update(currentAge => currentAge + 1);
}
Такие обновления также называют «обновлениями на уровне поля». Они автоматически передаются сигналу модели, поддерживая синхронизацию обоих источников.
Пример: загрузка данных из API
Распространённый подход — загрузить данные и заполнить ими модель:
export class UserProfileComponent {
userModel = signal({
name: '',
email: '',
bio: '',
});
userForm = form(this.userModel);
private userService = inject(UserService);
ngOnInit() {
this.loadUserProfile();
}
async loadUserProfile() {
const userData = await this.userService.getUserProfile();
this.userModel.set(userData);
}
}
Поля формы автоматически обновляются при изменении модели и отображают загруженные данные без дополнительного кода.
Двусторонняя привязка данных
Директива [formField] автоматически синхронизирует модель, состояние формы и пользовательский интерфейс в обоих направлениях.
Как передаются данные
Изменения передаются в обоих направлениях:
Ввод пользователя → модель:
- Пользователь вводит текст в элемент ввода
- Директива
[formField]обнаруживает изменение - Обновляется состояние поля
- Обновляется сигнал модели
Программное обновление → пользовательский интерфейс:
- Код обновляет модель с помощью
set()илиupdate() - Сигнал модели уведомляет подписчиков
- Обновляется состояние поля
- Директива
[formField]обновляет элемент ввода
Эта синхронизация выполняется автоматически. Вам не нужно создавать подписки или обработчики событий, чтобы поддерживать синхронизацию модели и пользовательского интерфейса.
Пример: оба направления
@Component({
template: `
<input type="text" [formField]="userForm.name" />
<button (click)="setName('Bob')">Set Name to Bob</button>
<p>Current name: {{ userModel().name }}</p>
`,
})
export class UserComponent {
userModel = signal({name: ''});
userForm = form(this.userModel);
setName(name: string) {
this.userForm.name().value.set(name);
// Input automatically displays 'Bob'
}
}
Когда пользователь вводит текст, обновляется userModel().name. При нажатии кнопки значение поля ввода меняется на «Bob». Код для ручной синхронизации не требуется.
Варианты структуры модели
Модели форм могут быть плоскими объектами или содержать вложенные объекты и массивы. Выбранная структура влияет на способы доступа к полям и организации проверки.
Плоские и вложенные модели
В плоских моделях форм все поля находятся на верхнем уровне:
// Flat structure
const userModel = signal({
name: '',
email: '',
street: '',
city: '',
state: '',
zip: '',
});
Во вложенных моделях связанные поля объединены в группы:
// Nested structure
const userModel = signal({
name: '',
email: '',
address: {
street: '',
city: '',
state: '',
zip: '',
},
});
Используйте плоскую структуру, если:
- У полей нет очевидных смысловых групп
- Нужен более простой доступ к полям (
userForm.cityвместоuserForm.address.city) - Правила проверки охватывают несколько потенциальных групп
Используйте вложенную структуру, если:
- Поля образуют очевидную смысловую группу (например, адрес)
- Сгруппированные данные соответствуют структуре API
- Нужно проверять группу целиком
Работа с вложенными объектами
Доступ к вложенным полям можно получить, указав путь к объекту:
const userModel = signal({
profile: {
firstName: '',
lastName: '',
},
settings: {
theme: 'light',
notifications: true,
},
});
const userForm = form(userModel);
// Access nested fields
userForm.profile.firstName; // FieldTree<string>
userForm.settings.theme; // FieldTree<string>
В шаблонах вложенные поля связываются так же, как поля верхнего уровня:
@Component({
template: `
<input [formField]="userForm.profile.firstName" />
<input [formField]="userForm.profile.lastName" />
<select [formField]="userForm.settings.theme">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
`,
}) Работа с массивами
Модели могут содержать массивы элементов:
const orderModel = signal({
customerName: '',
items: [{product: '', quantity: 0, price: 0}],
});
const orderForm = form(orderModel);
// Access array items by index
orderForm.items[0].product; // FieldTree<string>
orderForm.items[0].quantity; // FieldTree<number>
Элементы массивов, содержащие объекты, автоматически получают идентификаторы для отслеживания. Это помогает сохранять состояние полей, даже когда элементы меняют положение в массиве. Благодаря этому состояние проверки и взаимодействие пользователя сохраняются при изменении порядка элементов массива.
Дальнейшие шаги
В этом руководстве рассмотрены создание моделей и обновление значений. В связанных руководствах описаны другие аспекты 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/models