Reactive Forms

В прошлых темах был описан подход Template-Driven, который концентрировался вокруг шаблона компонента: для работы с формой и ее элементами в шаблоне компонента к элементам html применялись директивы NgModel и NgForm, правила валидации задавались в тегах элементов с помощью атрибутов required и pattern. Но есть альтернативный подход - использование реактивных форм (Reactive Forms). Рассмотрим, в чем он заключается.

При подходе Reactive Forms для формы создается набор объектов FormGroup и FormControl. Сама форма и ее подсекции представляют класс FormGroup, а отдельные элементы ввода - класс FormControl. Например, базовое создание формы:

this.form = new FormGroup();

Добавляем в форму элементы:

    this.form = new FormGroup(
      {
        email: new FormControl(),
        pass: new FormControl(),
        country: new FormControl(),
        answer: new FormControl()
      }
    );

Здесь определено четыре элемента: email, pass, country и userPhone.\

Объект FormControl может иметь различные формы определения. (Подробнее можно посмотреть в документации). В частности, в качестве первого параметра можно передавать значение по умолчанию для элемента, а в качестве второго параметра - набор валидаторов, при этом элементы формы можно группировать с помощью FormGroup:

  ngOnInit() {
    this.form = new FormGroup(
      {
        user: new FormGroup({
          email: new FormControl('anchikin@mail.ru', [Validators.email, Validators.required]),
          pass: new FormControl('', Validators.required),
        }),
        country: new FormControl('ru'),
        answer: new FormControl('no')
      }
    );
  }

Здесь к элементам применяется ряд валидаторов. Валидатор Validators.required требует обязательного наличия значения. Валидатор Validators.email проверяет, представляет ли введенная строка электронный адрес. Валидатор Validators.pattern(«[0-9]{10}») поверяет на соответствие регулярному выражению. Все встроенные валидаторы можно посмотреть в документации. Если валидаторов несколько, то они заключаются в массив.

Для привязки объекта myForm к конкретному элементу формы применяется атрибут formGroup:

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

Кроме того, необходимо связать объекты FormControl с элементами ввода с помощью атрибута formControlName:

<input type="text" class="form-control"  formControlName="email">

Данный элемент будет связан с объектом «email»: new FormControl('anchikin@mail.ru', [Validators.email, Validators.required]).

Теперь рассмотрим, как эти объекты будут взаимодействовать с шаблоном компонента. Для этого определим следующий компонент:

import { Component, OnInit } from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
 
  answers = [{
    type: 'yes',
    text: 'Да'
  }, {
    type: 'no',
    text: 'Нет'
  }];
 
  form: any;
 
  ngOnInit() {
    this.form = new FormGroup(
      {
        user: new FormGroup({
          email: new FormControl('anchikin@mail.ru', [Validators.email, Validators.required]),
          pass: new FormControl('', Validators.required),
        }),
        country: new FormControl('ru'),
        answer: new FormControl('no')
      }
    );
  }
 
  onSubmit() {
    console.log('Submited!', this.form);
  }
}
<div class="col-xs-8 col-xs-offset-2">
  <form [formGroup]="form" (ngSubmit)="onSubmit()">
 
    <div formGroupName="user">
      <div class="form-group" [ngClass]="{'has-error': form.get('user.email').invalid && form.get('user.email').touched}">
        <label>Email</label>
        <input type="text" class="form-control"  formControlName="email">
        <p class="help-block" *ngIf="form.get('user.email').invalid && form.get('user.email').touched">Не правильно прописан email</p>
      </div>
      <div class="form-group" [ngClass]="{'has-error': form.get('user.pass').invalid && form.get('user.pass').touched}">
        <label>Пароль</label>
        <input  type="password" class="form-control" formControlName="pass">
        <p class="help-block" *ngIf="form.get('user.pass').invalid && form.get('user.pass').touched">пароль не должен быть пустым!</p>
      </div>
    </div>
 
    <div class="form-group">
      <label>Выберите страну</label>
      <select class="form-control" formControlName="country">
        <option value="ru">Россия</option>
        <option value="by">Белоруссия</option>
        <option value="ua">Украина</option>
      </select>
    </div>
    <div class="radio" *ngFor="let ans of answers">
      <label>
        <input
          type="radio"
          name="answer"
          formControlName="answer"
          [value]="ans.type"
        > {{ans.text}}
      </label>
    </div>
    <button [disabled]="form.invalid" class="btn btn-success" type="submit">Сохранить</button>
  </form>
</div>

Для отображения ошибок валидации здесь используется параграфы с классом help-block, в которых определены выражения типа

<p class="help-block" *ngIf="form.get('user.email').invalid && form.get('user.email').touched">Не правильно прописан email</p>

С помощью выражений form.controls['user.email'] или form.get('user.email') мы можем обратиться к нужному элементу формы и получить его состояние или значение. В данном случае если значение поля ввода невалидно, и при этом поле ввода уже получало фокус, то отображается ошибка валидации.

Но чтобы все это заработало, необходимо импортировать модуль ReactiveFormsModule. Для этого изменим модуль приложения AppModule:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
 
import { AppComponent } from './app.component';
 
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }