Схемы и композиция схем
Signal Forms использует двухуровневую архитектуру, чтобы отделить структуру формы от её поведения во время выполнения.
Когда вы передаёте функцию схемы в form(), эта функция выполняется один раз при создании формы. Её задача — настроить дерево логики формы, объявив, какие поля имеют валидацию, какие поля отключены и какие поля зависят от других полей. Это структурный уровень вашей формы.
Внутри функции схемы вы вызываете функции правил, такие как disabled() и validate(). Эти функции правил принимают реактивную логику, которая пересчитывается при каждом изменении сигналов, на которые она ссылается. Условные правила, такие как disabled() и required(), принимают необязательную конфигурацию, включая функцию when, которая активирует правило. Вместе они образуют поведенческий уровень вашей формы во время выполнения.
contactForm = form(this.contactModel, (schemaPath) => {
// Schema function: runs ONCE during form creation
required(schemaPath.name);
disabled(schemaPath.couponCode, {when: ({valueOf}) => valueOf(schemaPath.total) < 50});
// ^^^ Reactive logic: recomputes when total changes
});
Это различие важно при композиции схем, поскольку такие функции, как apply(), applyWhen() и schema(), работают на структурном уровне. Схемы определяют, какие правила существуют и активны ли они, а функции правил определяют, что проверяют эти правила.
Создание повторно используемых схем с помощью schema()
Если несколько форм используют одни и те же правила для общей структуры данных, можно воспользоваться функцией schema(), чтобы выделить эти правила в повторно используемую схему.
import {schema, required, minLength} from '@angular/forms/signals';
const nameSchema = schema<{first: string; last: string}>((name) => {
required(name.first);
required(name.last);
minLength(name.first, 2);
minLength(name.last, 2);
});
Функция schema() оборачивает функцию и преобразует её в повторно используемый объект Schema<T>. Как и любая функция схемы, она выполняется один раз для каждой формы, но сам объект можно использовать в любом количестве форм.
СОВЕТ: Если правила нужны только в одном месте, встроенная функция схемы подойдёт не хуже. Используйте schema(), если хотите повторно использовать одну и ту же схему в нескольких формах или применить её к нескольким путям. Повторно используемые объекты Schema кэшируются при компиляции каждой формы.
Применение схемы с помощью apply()
Чтобы применить повторно используемую схему к определённому пути в форме, воспользуйтесь функцией apply(). При вызове apply() схема получает ограниченный путь, который видит только поля внутри этого подпути:
import {apply} from '@angular/forms/signals';
profileForm = form(this.profileModel, (schemaPath) => {
apply(schemaPath.name, nameSchema);
});
registrationForm = form(this.registrationModel, (schemaPath) => {
apply(schemaPath.name, nameSchema);
}); Условные схемы с помощью applyWhen()
ПРИМЕЧАНИЕ: В руководстве Добавление логики в форму была представлена функция applyWhen() для условных правил со встроенной логикой. В этом разделе описано, как сочетать applyWhen() с повторно используемыми схемами.
Некоторые правила должны применяться только при определённых условиях. Например, для поля почтового индекса может требоваться проверка только в том случае, если выбрана страна США.
Функция applyWhen() применяет схему условно, в зависимости от реактивного состояния. Она принимает три аргумента:
- Путь, к которому нужно применить схему
- Функцию реактивной логики, возвращающую
true, когда схема должна быть активна - Схему или функцию схемы, содержащую условные правила
import {form, applyWhen, required, pattern} from '@angular/forms/signals';
addressForm = form(this.addressModel, (schemaPath) => {
applyWhen(
schemaPath,
({valueOf}) => valueOf(schemaPath.country) === 'US',
(schemaPath) => {
required(schemaPath.zipCode);
pattern(schemaPath.zipCode, /^\d{5}(-\d{4})?$/);
},
);
});
Функция логики получает FieldContext, который предоставляет доступ к value, valueOf, stateOf и другим реактивным помощникам. Поскольку функция реактивна, условие переоценивается при каждом изменении сигналов, которые она считывает. Когда условие становится false, правила внутри схемы деактивируются. Когда оно снова становится true, они активируются.
Сама схема по-прежнему является структурной — функция схемы выполняется один раз при создании формы. Условие определяет, активны ли эти правила, а не существуют ли они.
Внутри условной схемы используйте параметр ограниченного пути, переданный этой функции схемы. Пути из внешней схемы недопустимы внутри вложенной схемы.
Сочетание applyWhen() с повторно используемыми схемами
Поскольку applyWhen() принимает объект Schema, его можно сочетать с schema(), чтобы условно применять повторно используемые схемы:
const usZipCodeSchema = schema<{zipCode: string}>((address) => {
required(address.zipCode);
pattern(address.zipCode, /^\d{5}(-\d{4})?$/);
});
const caPostalCodeSchema = schema<{postalCode: string}>((address) => {
required(address.postalCode);
pattern(address.postalCode, /^[A-Z]\d[A-Z] \d[A-Z]\d$/);
});
shippingForm = form(this.shippingModel, (schemaPath) => {
applyWhen(
schemaPath.address,
({valueOf}) => valueOf(schemaPath.country) === 'US',
usZipCodeSchema,
);
applyWhen(
schemaPath.address,
({valueOf}) => valueOf(schemaPath.country) === 'CA',
caPostalCodeSchema,
);
});
ПРИМЕЧАНИЕ: Функция логики обращается к valueOf(schemaPath.country), несмотря на то что аргумент пути — schemaPath.address. Это связано с тем, что помощник valueOf может обращаться к любому полю формы, а не только к полям внутри ограниченного пути.
Этот подход позволяет сохранить модульность логики валидации: правила для адресов каждой страны находятся в отдельной схеме, а форма выбирает, какую из них активировать, исходя из выбора пользователя.
Сужение типов с помощью applyWhenValue()
Функция applyWhenValue() упрощает условия, которым нужно проверять только значение поля. Вместо FieldContext функция условия получает непосредственно исходное значение поля.
applyWhen(schemaPath.payment, ({value}) => value().type === 'credit-card', creditCardSchema);
applyWhenValue(schemaPath.payment, (payment) => payment.type === 'credit-card', creditCardSchema);
Главное преимущество applyWhenValue() — поддержка защитников типов TypeScript. Если функция условия является защитником типа, параметр типа схемы сужается до защищённого типа. Это особенно полезно для дискриминируемых объединений, в которых у каждого варианта есть разные поля, требующие разных правил.
import {form, applyWhenValue, required} from '@angular/forms/signals';
interface CreditCard {
type: 'credit-card';
cardNumber: string;
expiry: string;
cvv: string;
}
interface BankTransfer {
type: 'bank-transfer';
accountNumber: string;
routingNumber: string;
}
type PaymentMethod = CreditCard | BankTransfer;
function isCreditCard(value: PaymentMethod): value is CreditCard {
return value.type === 'credit-card';
}
function isBankTransfer(value: PaymentMethod): value is BankTransfer {
return value.type === 'bank-transfer';
}
paymentForm = form(this.paymentModel, (schemaPath) => {
applyWhenValue(schemaPath, isCreditCard, (payment) => {
// TypeScript knows payment is scoped to CreditCard
required(payment.cardNumber);
required(payment.expiry);
required(payment.cvv);
});
applyWhenValue(schemaPath, isBankTransfer, (payment) => {
// TypeScript knows payment is scoped to BankTransfer
required(payment.accountNumber);
required(payment.routingNumber);
});
});
Без защитника типа TypeScript не знал бы, какие поля доступны внутри каждой функции схемы. Сужение типа гарантирует, что обращение к payment.cardNumber безопасно с точки зрения типов в ветви кредитной карты, а обращение к payment.accountNumber — в ветви банковского перевода.
Элементы массива с помощью applyEach()
Если форма содержит массив объектов, часто требуется применить одни и те же правила ко всем элементам. Функция applyEach() применяет схему к каждому элементу поля-массива независимо от количества элементов.
import {form, applyEach, required, min} from '@angular/forms/signals';
type LineItem = {name: string; quantity: number};
orderForm = form(this.orderModel, (schemaPath) => {
required(schemaPath.title);
applyEach(schemaPath.items, (item) => {
required(item.name);
min(item.quantity, 1);
});
});
Функция схемы, переданная в applyEach(), получает SchemaPathTree, ограниченный одним элементом массива. Объявленные внутри правила применяются ко всем элементам массива, в том числе к элементам, добавленным после создания формы.
Сочетание applyEach() с повторно используемыми схемами
Поскольку applyEach() принимает объект Schema, можно выделить правила для элементов в повторно используемую схему и применять её в разных формах:
const lineItemSchema = schema<LineItem>((item) => {
required(item.name);
min(item.quantity, 1);
});
orderForm = form(this.orderModel, (schemaPath) => {
required(schemaPath.title);
applyEach(schemaPath.items, lineItemSchema);
});
invoiceForm = form(this.invoiceModel, (schemaPath) => {
required(schemaPath.invoiceNumber);
applyEach(schemaPath.lineItems, lineItemSchema);
});
СОВЕТ: Подробнее о валидации элементов массива, включая пользовательские сообщения об ошибках для каждого поля, см. в руководстве по валидации.
Дальнейшие шаги
Чтобы узнать больше о 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/schemas