В чём разница между Reactive Forms и Template-driven формами?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между Reactive Forms и Template-driven формами
Этот вопрос касается Angular. Это два подхода к работе с формами в Angular с разными уровнями контроля и сложности.
Template-driven формы (TDF)
Template-driven — это старый, простой подход, где форма управляется в HTML шаблоне с помощью директив.
<!-- template-driven-form.component.html -->
<form (ngSubmit)="onSubmit()" #myForm="ngForm">
<!-- Двухсторонняя привязка с ngModel -->
<input
type="email"
name="email"
[(ngModel)]="user.email"
required
email
/>
<span *ngIf="myForm.get('email')?.hasError('email')">Email не валиден</span>
<input
type="password"
name="password"
[(ngModel)]="user.password"
required
minlength="8"
/>
<select name="country" [(ngModel)]="user.country" required>
<option>Россия</option>
<option>США</option>
</select>
<button type="submit" [disabled]="!myForm.valid">Отправить</button>
</form>
// template-driven-form.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-template-form',
templateUrl: './template-driven-form.component.html',
})
export class TemplateDrivenFormComponent {
user = {
email: '',
password: '',
country: '',
};
onSubmit() {
console.log('Form data:', this.user);
// Отправляем на сервер
}
}
Характеристики Template-driven:
- Логика в HTML (директивы ngModel, ngForm)
- Простые формы
- Меньше кода
- Меньше контроля
- Асинхронная валидация сложнее
Reactive Forms (RF)
Reactive Forms — это современный, мощный подход, где форма управляется в TypeScript коде через FormBuilder/FormGroup.
// reactive-form.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-reactive-form',
templateUrl: './reactive-form.component.html',
})
export class ReactiveFormComponent implements OnInit {
form: FormGroup;
constructor(private fb: FormBuilder) {
// Создаём форму программно
this.form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
country: ['', Validators.required],
});
}
ngOnInit() {
// Подписываемся на изменения
this.form.get('email')?.valueChanges.subscribe((value) => {
console.log('Email изменён:', value);
});
}
onSubmit() {
if (this.form.valid) {
console.log('Form data:', this.form.value);
// Отправляем на сервер
}
}
}
<!-- reactive-form.component.html -->
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<!-- Привязка к контролю без ngModel -->
<input
type="email"
formControlName="email"
placeholder="Email"
/>
<!-- Проверка ошибок через контроль -->
<span *ngIf="form.get('email')?.hasError('email')">Email не валиден</span>
<input
type="password"
formControlName="password"
placeholder="Пароль"
/>
<span *ngIf="form.get('password')?.hasError('minlength')">
Минимум 8 символов
</span>
<select formControlName="country">
<option value="">Выберите страну</option>
<option value="ru">Россия</option>
<option value="us">США</option>
</select>
<button type="submit" [disabled]="!form.valid">Отправить</button>
</form>
Сравнение подходов
| Аспект | Template-driven | Reactive |
|---|---|---|
| Логика | В HTML | В TypeScript |
| Сложность | Простые | Сложные |
| Код | Меньше | Больше, но понятнее |
| Контроль | Низкий | Высокий |
| Тестирование | Сложнее | Легче |
| Асинхронная валидация | Сложно | Просто |
| Динамические поля | Сложно | Просто |
| Производительность | Медленнее | Быстрее |
| RxJS | Не требует | Требует знаний |
Практические примеры
1. Асинхронная валидация (проверка email в БД):
// Reactive Forms — просто
import { AbstractControl, AsyncValidator, ValidationErrors } from '@angular/forms';
import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class EmailValidator implements AsyncValidator {
constructor(private http: HttpClient) {}
validate(control: AbstractControl) {
return this.http.get(`/api/check-email/${control.value}`).pipe(
map((result: any) => (result.exists ? { emailTaken: true } : null))
);
}
}
// Использование в форме
this.form = this.fb.group({
email: [
'',
[Validators.required, Validators.email],
[this.emailValidator], // Асинхронная валидация
],
});
2. Динамические поля (добавление/удаление):
// Reactive Forms — встроенная поддержка
this.form = this.fb.group({
items: this.fb.array([
this.fb.group({ name: '', price: '' }),
]),
});
get items() {
return this.form.get('items') as FormArray;
}
addItem() {
this.items.push(
this.fb.group({ name: '', price: '' })
);
}
removeItem(index: number) {
this.items.removeAt(index);
}
<div formArrayName="items">
<div *ngFor="let item of items.controls; let i = index" [formGroupName]="i">
<input formControlName="name" placeholder="Название" />
<input formControlName="price" placeholder="Цена" />
<button (click)="removeItem(i)">Удалить</button>
</div>
<button (click)="addItem()">Добавить товар</button>
</div>
3. Реактивные обновления:
// Reactive Forms — RxJS операторы
this.form.get('email')?.valueChanges
.pipe(
debounceTime(300), // Подождать 300мс
distinctUntilChanged(), // Только если значение изменилось
switchMap((email) => this.http.get(`/api/check-email/${email}`))
)
.subscribe((result) => {
console.log('Email статус:', result);
});
// Комбинирование нескольких полей
combineLatest([
this.form.get('password')?.valueChanges,
this.form.get('confirmPassword')?.valueChanges,
])
.pipe(
map(([pass, confirm]) => pass === confirm),
)
.subscribe((match) => {
if (!match) {
this.form.setErrors({ passwordMismatch: true });
}
});
Когда использовать?
Template-driven (TDF):
- Очень простые формы (логин, поиск)
- Быстрый прототип
- Немного полей
- Новичок в Angular
<form #form="ngForm" (ngSubmit)="login(form.value)">
<input type="email" name="email" [(ngModel)]="email" />
<input type="password" name="password" [(ngModel)]="password" />
<button [disabled]="form.invalid">Вход</button>
</form>
Reactive Forms (RF):
- Сложные формы (регистрация, профиль, таблица)
- Асинхронная валидация
- Динамические поля
- Опытный разработчик
- Нужны unit-тесты
this.form = this.fb.group({
email: [
'',
[Validators.required, Validators.email],
[this.emailValidator],
],
password: ['', [Validators.required, Validators.minLength(8)]],
confirmPassword: [''],
}, { validators: passwordMatchValidator });
Миграция с TDF на Reactive
// Было (TDF)
export class LoginComponent {
email = '';
password = '';
}
// Стало (RF)
export class LoginComponent {
form: FormGroup;
constructor(fb: FormBuilder) {
this.form = fb.group({
email: ['', Validators.required],
password: ['', Validators.required],
});
}
}
Резюме
Template-driven формы — простой подход с логикой в HTML, подходит для простых форм. Reactive Forms — мощный подход с логикой в TypeScript, подходит для сложных форм с асинхронной валидацией и динамическими полями. Современная практика Angular рекомендует использовать Reactive Forms, так как они обеспечивают лучший контроль, тестируемость и масштабируемость.