Angular IDPStrong Junior
Модулі / Форми / Будівельні блоки
🔥 0

Модуль 6 · урок 2 з 12

Будівельні блоки

Реактивна форма складається з трьох цеглин, які вкладаються одна в одну. Зрозумівши їх, ти зможеш описати форму будь-якої складності — від двох полів до дерева з динамічними секціями.

FormControl — одне поле

const title = new FormControl('', {
  nonNullable: true,                     // про це наступний урок
  validators: [Validators.required, Validators.maxLength(120)],
});

title.value;          // поточне значення
title.setValue('Нова назва');
title.patchValue('…'); // для контролу те саме, різниця буде в групі
title.reset();         // повернути до початкового

title.valid;           // пройшов валідацію
title.errors;          // { required: true } або null
title.touched;         // користувач заходив і виходив із поля
title.dirty;           // значення міняли

title.disable();       // вимкнути — і виключити зі значення форми
title.enable();

FormGroup — обʼєкт із полів

const form = new FormGroup({
  code: new FormControl('', { nonNullable: true }),
  title: new FormControl('', { nonNullable: true }),
  estimate: new FormControl(1, { nonNullable: true }),
});

form.value;                // { code, title, estimate } — але без вимкнених полів
form.getRawValue();        // усі поля, включно з вимкненими
form.controls.code;        // доступ до контролу — типізований
form.patchValue({ title: 'Нова' });   // оновити частину полів
form.setValue({ … });      // оновити ВСІ поля — інакше помилка
value чи getRawValue — важлива пастка

form.value не містить вимкнених полів. Тобто якщо поле code заблоковане при редагуванні, у value його просто не буде — і на бекенд поїде обʼєкт без коду. Правило просте: для відправки на сервер завжди getRawValue().

FormArray — список однотипних

const form = new FormGroup({
  title: new FormControl('', { nonNullable: true }),
  subtasks: new FormArray<FormControl<string>>([]),
});

// додати
form.controls.subtasks.push(new FormControl('', { nonNullable: true }));

// видалити
form.controls.subtasks.removeAt(2);

// перебрати в шаблоні
form.controls.subtasks.controls;   // масив контролів

FormBuilder — щоб не писати new

private readonly fb = inject(FormBuilder);

protected readonly form = this.fb.nonNullable.group({
  code: ['', [Validators.required, Validators.pattern(/^[A-Z]{3,6}-\d+$/)]],
  title: ['', [Validators.required, Validators.maxLength(120)]],
  estimate: [1, [Validators.min(0.5), Validators.max(200)]],
  assigneeId: this.fb.control<string | null>(null),
  subtasks: this.fb.array<FormControl<string>>([]),
});

fb.nonNullable — саме те, що потрібно: усі контроли створюються з nonNullable: true, і не доводиться писати це в кожному рядку. Наступний урок пояснить, чому це принципово.

Прив'язка в шаблоні

<form [formGroup]="form" (ngSubmit)="save()">

  <label for="code">Код</label>
  <input id="code" formControlName="code">

  <div formGroupName="meta">              <!-- вкладена група -->
    <input formControlName="tag">
  </div>

  <div formArrayName="subtasks">
    @for (ctrl of form.controls.subtasks.controls; track $index) {
      <input [formControlName]="$index">
    }
  </div>

  <button type="submit" [disabled]="form.invalid">Зберегти</button>
</form>
FormGroup — уся форма FormControl code FormControl title FormGroup meta — вкладена FormArray subtasks — список Статус батька рахується з дітей: група невалідна, якщо невалідна хоч одна дитина. Тому form.valid — це підсумок усього дерева, і окремо перевіряти кожне поле не треба.
Форма — це дерево контролів. Значення й статус піднімаються знизу вгору, тому корінь завжди знає підсумок.