Как использовать формы в 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: \`&lt;input \[value\]="value" (input)="onChange($event.target.value)" /&gt;\`,
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. Выбор подхода зависит от сложности формы, требований к масштабируемости и предпочтений команды.