Модуль 6 · урок 6 з 12
Динамічні правила
«Dynamic validation rules based on entity status» — другий пункт цілі про валідацію. Ідея проста: правила залежать від стану форми. Заблоковану задачу не можна зберегти без причини, а завершену — без фактичних годин.
Реакція на зміну поля
constructor() {
this.form.controls.status.valueChanges.pipe(
startWith(this.form.controls.status.value),
takeUntilDestroyed(),
).subscribe(status => this.applyRules(status));
}
private applyRules(status: TaskStatus): void {
const reason = this.form.controls.blockReason;
const spent = this.form.controls.spentHours;
if (status === 'blocked') {
reason.addValidators(Validators.required);
} else {
reason.removeValidators(Validators.required);
reason.setValue('');
}
if (status === 'done') {
spent.addValidators([Validators.required, Validators.min(0.1)]);
} else {
spent.clearValidators();
}
reason.updateValueAndValidity(); // без цього нічого не зміниться
spent.updateValueAndValidity();
}
startWith — щоб правила застосувались одразу при відкритті форми
на редагування, а не лише після зміни статусу користувачем.
updateValueAndValidity() — Angular не перевіряє контрол автоматично
після зміни набору валідаторів. Забути цей виклик — класична причина «валідатор не працює».
Показувати чи ховати поле
@if (form.controls.status.value === 'blocked') {
<div class="field">
<label for="reason">Причина блокування</label>
<textarea id="reason" formControlName="blockReason"></textarea>
</div>
}
Тут є нюанс, який часто пропускають: прибрати поле з шаблону не означає прибрати його
з форми. Контрол лишається, його значення й далі потрапляє у getRawValue(),
а валідатор і далі блокує форму — тільки користувач не бачить, чому.
// правильно: разом із приховуванням — прибрати валідатори й очистити
private toggleReason(show: boolean): void {
const c = this.form.controls.blockReason;
if (show) {
c.addValidators(Validators.required);
} else {
c.clearValidators();
c.setValue('');
}
c.updateValueAndValidity();
}
disable проти прихованого поля
control.disable(); // поле неактивне, валідація не працює, значення НЕ в form.value
control.enable();
| Треба | Як | Що зі значенням |
|---|---|---|
| Поле видно, але змінювати не можна | disable() | Зникає з value, лишається в getRawValue() |
| Поле видно, редагується, але не обовʼязкове | Прибрати required | Лишається |
| Поля взагалі не має бути | @if + очистити й зняти валідатори | Треба обнулити вручну |
| Поле лише для читання, але має відправлятись | readonly в шаблоні | Лишається в value |
disable() робить дві речі одразу: блокує введення і виключає поле
зі значення форми. Якщо тобі треба лише перше — бери readonly в шаблоні.
Інакше поле, яке ти показав користувачу неактивним, просто не поїде на бекенд.
Залежні списки
// вибір проєкту визначає доступні епізоди
readonly episodes = toSignal(
this.form.controls.showId.valueChanges.pipe(
startWith(this.form.controls.showId.value),
switchMap(showId => showId ? this.api.episodes(showId) : of([])),
),
{ initialValue: [] },
);
constructor() {
this.form.controls.showId.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => {
this.form.controls.episodeId.setValue(null); // скинути залежне поле
});
}
Скидання залежного поля — обовʼязкове. Інакше можна зберегти епізод із одного проєкту, вибравши інший проєкт: у формі буде видно нову назву, а в даних лишиться старий ідентифікатор.