Ось готовий урок, створений у стилі CS50, спеціально для теми Reactive Forms та FormBuilder в Angular.
🎓 CS50: Reactive Forms та FormBuilder (Angular)
Вітаю, друзі! Це... ні, це не Гарвард, але ми тут, щоб розібратися з однією з найпотужніших концепцій у світі Frontend-розробки.
Мене звуть [Ваше ім’я / ШІ-Інструктор], і сьогодні ми поговоримо про контроль. Тотальний контроль над даними.
1. 🔥 Вступ: Хаос у формах
Уявіть собі ситуацію. Ви пишете форму реєстрації для нового Facebook або TikTok. У вас є поля: логін, пароль, повтор пароля, адреса, чекбокс "Я згоден продати душу"...
Спочатку все просто. Ви кидаєте input в HTML, вішаєте [(ngModel)] (якщо ви вже знайомі з template-driven підходом), і все працює.
Але раптом приходить менеджер і каже:
"Слухай, зроби так, щоб поле 'Адреса' зникало, якщо користувач не поставив галочку 'Хочу доставку'. А якщо пароль коротший за 8 символів, кнопка 'Надіслати' має бути червоною. І ще, як тільки він вводить email, ми маємо фоново перевіряти, чи він вільний, не чекаючи натискання кнопки."
Ви починаєте ліпити купу *ngIf, купу змінних у класі, купу обробників подій... Ваш код перетворюється на спагеті. 🍝
Риторичне запитання: Чи не було б круто, якби у вас був пульт керування, де вся логіка форми жила б в одному місці (у вашому TypeScript коді), а HTML був би просто "дурним" екраном, який це показує?
Ось навіщо нам Reactive Forms. Це не просто спосіб отримати дані. Це спосіб керувати станом форми програно, чітко і реактивно.
Аналогія: * Template-driven форми — це як заповнювати паперову анкету в держустанові. Ви пишете, що хочете, а помилку помітять, тільки коли ви віддасте листок у віконце. * Reactive Forms — це як розумний інтерфейс Джарвіса у Залізної Людини. Система стежить за кожним вашим рухом у реальному часі, підказує, блокує або дозволяє дії миттєво.
2. 🧠 Теоретична база (Що під капотом?)
Давайте заглянемо під капот. У Reactive Forms ми не покладаємося на HTML. Ми створюємо модель форми у коді компонента.
Тут є три головні герої, яких треба знати в обличчя:
1. FormControl (Атом) ⚛️
Це найдрібніша частинка. Одне поле введення. * Він знає своє значення (наприклад, "admin"). * Він знає свій статус (VALID, INVALID, PENDING). * Він знає, чи його торкалися (touched/untouched).
2. FormGroup (Молекула) 🧬
Це контейнер для FormControl-ів.
* Якщо у вас є форма логіна (email + пароль), це — група.
* Магія: Якщо хоч один контрол всередині INVALID, то вся група автоматично стає INVALID.
3. FormBuilder (Архітектор) 👷♂️
Це сервіс-помічник.
* Створювати контрол так: new FormControl('', Validators.required) і групувати їх вручну — це довго і нудно.
* FormBuilder — це "синтаксичний цукор", який дозволяє писати менше коду, але робити те саме.
💡 Що зрозуміти інтуїтивно:
У Reactive Forms джерело правди — це TypeScript код. HTML-шаблон просто "підписується" на ваш код. Змінили код — змінився шаблон. Змінили щось у шаблоні — код про це дізнався миттєво через потоки (Streams/RxJS).
3. 🧪 Приклади (Step-by-step)
Приклад 1: "Хардкорний" спосіб (Без FormBuilder)
Спочатку подивіться, як це робиться "в лоб", щоб зрозуміти, навіщо нам спрощення.
// component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
@Component({...})
export class MyFormComponent {
// Ми створюємо інстанси класів вручну
loginForm = new FormGroup({
email: new FormControl('', Validators.required),
password: new FormControl('')
});
onSubmit() {
console.log(this.loginForm.value);
}
}
В HTML ми просто "зв'язуємо" їх:
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<input formControlName="email" placeholder="Email">
<input formControlName="password" placeholder="Password">
<button type="submit">Увійти</button>
</form>
Питання до вас: Виглядає непогано, так? Але що, якщо у нас 20 полів? Писати new FormControl(...) 20 разів? В очах зарябіє від слова new.
Приклад 2: Елегантний спосіб (З FormBuilder) 🎩
Ось тут виходить на сцену наш герой — FormBuilder.
// component.ts
import { Component, inject } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms'; // Імпортуємо FormBuilder
@Component({...})
export class BetterFormComponent {
private fb = inject(FormBuilder); // Інжектуємо сервіс (або через конструктор)
// Дивіться, як чисто! Жодних "new FormControl"
profileForm = this.fb.group({
username: ['', Validators.required], // [значення, валідатори]
bio: [''],
address: this.fb.group({ // Вкладена група!
city: ['Kyiv'],
street: ['']
})
});
save() {
// Ми можемо перевірити валідність усієї форми одним махом
if (this.profileForm.valid) {
console.log('Дані готові до відправки:', this.profileForm.value);
} else {
console.log('Форма невалідна!');
}
}
}
Що ми отримали? 1. Код читається як звичайна структура об'єкта. 2. Легко робити вкладеність (адреса всередині профілю). 3. Валідація додається другим параметром у масиві.
4. 🛠 Практична частина
Прийшов час забруднити руки кодом. Відкривайте IDE (або StackBlitz).
🔹 Завдання 1: Створіть форму замовлення піци
Використовуйте FormBuilder.
Поля:
1. size (Розмір: "Маленька", "Велика"). За замовчуванням "Велика".
2. topping (Начинка).
3. contactInfo (Вкладена група):
* phone (обов'язкове поле).
* address.
🔹 Завдання 2: Реакція на зміни
У Reactive Forms ми можемо підписатися на зміни!
Додайте в ngOnInit такий код (адаптуйте під свою форму):
this.pizzaForm.get('size')?.valueChanges.subscribe(val => {
console.log(`Ви змінили розмір піци на: ${val}`);
});
Мета: Побачити в консолі повідомлення кожного разу, коли ви змінюєте розмір.
🔹 Завдання 3: "А що, якщо..." (Динамічне керування)
Зробіть так, щоб поле address було обов'язковим (Validators.required), тільки якщо обрано розмір "Велика".
Підказка: Вам потрібно слухати valueChanges поля size і використовувати методи setValidators або clearValidators для поля address.
🔹 Завдання 4: Міні-кейс "Кнопка Reset"
Додайте кнопку "Очистити". При кліку форма має скинутися до початкового стану (наприклад, розмір знову має стати "Велика", а не порожнім).
Підказка: Метод .reset({ size: 'Велика' }).
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі в Angular-формах?
1. Типізація (Strict Typed Forms)
Новачки часто працюють з формами як з any.
Профі знає, що з Angular 14+ форми строго типізовані.
// Замість загального FormGroup, Angular сам виводить тип:
profileForm = this.fb.group({
name: new FormControl<string | null>(null)
});
// Тепер this.profileForm.value.name — це точно string або null, а не будь-що.
2. Не мішайте стилі
Помилка: Використовувати [(ngModel)] всередині форми, яка вже керується [formGroup].
Як треба: Якщо ви обрали Reactive Forms — використовуйте formControlName. Забудьте про ngModel.
3. Логіка в класі, а не в шаблоні
Якщо ви пишете в HTML щось типу <div *ngIf="form.get('email').invalid && form.get('email').touched">, це ок для навчання.
Профі винесе це в гетер:
get isEmailInvalid() {
const email = this.profileForm.get('email');
return email?.invalid && email?.touched;
}
І в HTML буде просто: *ngIf="isEmailInvalid". Чистота — запорука здоров'я коду!
6. 🧩 Підсумок
Отже, що ми маємо сьогодні в сухому залишку?
- Reactive Forms дають нам повний контроль над даними в компоненті (TypeScript), а не в шаблоні (HTML).
- FormControl — це одиниця даних.
- FormGroup — це група одиниць.
- FormBuilder — це наш найкращий друг, який економить час на написанні коду.
- Тепер ви можете підписуватися на зміни полів (
valueChanges) як на стрім YouTube!
Ви тепер вмієте: Створювати складні, вкладені форми, валідувати їх і реагувати на зміни користувача в реальному часі.
🚀 Тизер наступного уроку
Але стривайте! А що, якщо користувач хоче додати не одну піцу, а п'ять? Або десять? Ми ж не будемо створювати pizza1, pizza2, pizza3 вручну?
На наступному уроці ми розберемо FormArray — спосіб динамічно додавати та видаляти поля форми прямо під час роботи програми.
А поки що — це був CS50... тобто, курс по Angular. Кодьте із задоволенням! 👋