Как использовать формы в Angular (Template-driven и Reactive forms)?
В Angular существует два основных подхода для создания и управления формами: Template-driven forms (формы, управляемые шаблоном) и Reactive forms (реактивные формы). Оба подхода реализованы в отдельных модулях (FormsModule и ReactiveFormsModule) и подходят для разных задач. Template-driven формы хорошо подходят для простых случаев, когда логика формы может быть описана прямо в шаблоне. Reactive forms — более масштабируемые, предсказуемые и лучше подходят для сложных, динамических и валидируемых форм.
1. Подключение модулей
-
Для Template-driven форм: FormsModule
-
Для Reactive форм: ReactiveFormsModule
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: \[
FormsModule,
ReactiveFormsModule
\]
})
export class AppModule {}
2. Template-driven Forms
2.1. Основные концепции
Template-driven формы основаны на директивах и двусторонней привязке ([(ngModel)]). Вся логика формы описывается в HTML-шаблоне.
2.2. Пример использования
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<input name="username" \[(ngModel)\]="user.username" required />
<input type="email" name="email" \[(ngModel)\]="user.email" required />
<button type="submit" \[disabled\]="userForm.invalid">Submit</button>
</form>
export class MyComponent {
user = { username: '', email: '' };
onSubmit(form: NgForm) {
console.log(form.value);
}
}
2.3. Валидация
- В шаблоне:
<input name="email" \[(ngModel)\]="user.email" required email />
<div \*ngIf="emailRef.invalid && emailRef.touched">Неверный email</div>
- Ссылки на поля:
<input #emailRef="ngModel" name="email" \[(ngModel)\]="user.email" required />
3. Reactive Forms
3.1. Основные концепции
Reactive формы управляются в классе компонента с использованием FormGroup, FormControl и FormBuilder. Они более декларативные и удобны для сложной валидации, динамических форм, вложенности и реактивного программирования.
3.2. Пример формы
import { FormGroup, FormControl } from '@angular/forms';
export class MyComponent {
form = new FormGroup({
username: new FormControl(''),
email: new FormControl('')
});
onSubmit() {
console.log(this.form.value);
}
}
<form \[formGroup\]="form" (ngSubmit)="onSubmit()">
<input formControlName="username" />
<input formControlName="email" />
<button type="submit" \[disabled\]="form.invalid">Send</button>
</form>
4. Использование FormBuilder
Сокращение синтаксиса:
import { FormBuilder } from '@angular/forms';
constructor(private fb: FormBuilder) {}
form = this.fb.group({
username: \[''\],
email: \[''\]
});
5. Валидация в Reactive Forms
5.1. Синхронные валидаторы
import { Validators } from '@angular/forms';
this.form = this.fb.group({
email: \['', \[Validators.required, Validators.email\]\],
password: \['', \[Validators.required, Validators.minLength(6)\]\]
});
5.2. Проверка состояния
<input formControlName="email" />
<div \*ngIf="form.get('email')?.invalid && form.get('email')?.touched">
Неверный email
</div>
6. Асинхронные валидаторы
import { AsyncValidatorFn } from '@angular/forms';
import { of, timer } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
const uniqueEmailValidator: AsyncValidatorFn = (control) => {
return timer(500).pipe(
switchMap(() => of(control.value === 'test@example.com')),
map(isTaken => isTaken ? { emailTaken: true } : null)
);
};
this.form = this.fb.group({
email: \['', \[Validators.required\], \[uniqueEmailValidator\]\]
});
7. Динамические формы
7.1. С массивом полей (FormArray)
import { FormArray } from '@angular/forms';
this.form = this.fb.group({
names: this.fb.array(\[\])
});
addName() {
(this.form.get('names') as FormArray).push(new FormControl(''));
}
<div formArrayName="names">
<div \*ngFor="let control of form.get('names')\['controls'\]; let i = index">
<input \[formControlName\]="i" />
</div>
</div>
<button (click)="addName()">Добавить</button>
8. Разделение на подформы (вложенные FormGroup)
this.form = this.fb.group({
user: this.fb.group({
name: \[''\],
email: \[''\]
}),
settings: this.fb.group({
notifications: \[true\]
})
});
<form \[formGroup\]="form">
<div formGroupName="user">
<input formControlName="name" />
<input formControlName="email" />
</div>
</form>
9. Слежение за изменениями
this.form.valueChanges.subscribe(value => {
console.log('Form changed:', value);
});
this.form.get('email')?.statusChanges.subscribe(status => {
console.log('Email status:', status);
});
10. Программное управление
Установка значения
this.form.patchValue({ username: 'Alex' });
this.form.get('email')?.setValue('alex@example.com');
Сброс формы
this.form.reset();
11. Общие директивы и теги
Директива / Атрибут | Назначение |
---|---|
formControl | Привязка к FormControl напрямую |
--- | --- |
formControlName | Привязка по имени в FormGroup |
--- | --- |
formGroup | Указывает FormGroup на элементе <form> |
--- | --- |
formArrayName | Привязка массива форм |
--- | --- |
ngModel | Template-driven привязка |
--- | --- |
required, min, etc. | HTML-валидация, работает с Angular валидацией |
--- | --- |
12. Сравнение Template-driven и Reactive
Характеристика | Template-driven | Reactive forms |
---|---|---|
Управление | Шаблон | Код компонента |
--- | --- | --- |
Подходит для | Простых форм | Сложных, динамических форм |
--- | --- | --- |
Валидация | Через атрибуты и шаблон | Через код и валидаторы |
--- | --- | --- |
Тестирование | Ограниченное | Простое и модульное |
--- | --- | --- |
Производительность | Ниже, из-за привязки в шаблоне | Выше, так как управление централизовано |
--- | --- | --- |
13. Использование ControlValueAccessor
Для создания собственных компонентов, совместимых с формами:
@Component({
selector: 'custom-input',
template: \`<input \[value\]="value" (input)="onChange($event.target.value)" />\`,
providers: \[{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}\]
})
export class CustomInputComponent implements ControlValueAccessor {
value = '';
onChange = (value: any) => {};
onTouched = () => {};
writeValue(value: any) {
this.value = value;
}
registerOnChange(fn: any) {
this.onChange = fn;
}
registerOnTouched(fn: any) {
this.onTouched = fn;
}
}
Оба подхода к созданию форм в Angular обеспечивают мощные возможности по работе с пользовательским вводом, валидацией, динамическими изменениями и интеграцией с внешними API. Выбор подхода зависит от сложности формы, требований к масштабируемости и предпочтений команды.