Angular IDPStrong Junior
Модулі / Форми / Динамічні правила
🔥 0

Модуль 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

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);   // скинути залежне поле
  });
}

Скидання залежного поля — обовʼязкове. Інакше можна зберегти епізод із одного проєкту, вибравши інший проєкт: у формі буде видно нову назву, а в даних лишиться старий ідентифікатор.

≈ 40 хв · +25 XP за урок