Angular でモデル駆動型フォームを実装してみた

Overview

Angular でフォームを実装してみた では, より手軽に実装可能な, テンプレート駆動型のフォームについて記載しました. テンプレート駆動型のフォームでは, 検証ルールをテンプレートに記述していました.

検証ルールはテンプレートに記述するだけではなく, コンポーネント側に記述することも可能です. これが, モデル駆動型 (Reactive 駆動型) のフォームです. テンプレート駆動型のフォームと比較すると, 冗長なコードになってしまいますが, より柔軟に, 複雑な検証ルールを実装できます.

ReactiveFormsModule

モデル駆動型のフォームを実装するには, FormsModule の代わりに, ReactiveFormsModule をインポートする必要があります.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

そして, テンプレートとコンポーネントは以下のようになります.

<form [formGroup]="myForm" (ngSubmit)="show()">
  <div>
    <label>メールアドレス: <input type="email" name="mail" [formControl]="mail" /></label>
    <span *ngIf="mail.errors?.required">メールアドレスは必須です.</span>
    <span *ngIf="mail.errors?.email">メールアドレスを正しい形式で入力してください.</span>
  </div>
  <div>
    <label>パスワード: <input type="password" name="password" [formControl]="password" /></label>
    <span *ngIf="password.errors?.required">パスワードは必須です.</span>
    <span *ngIf="password.errors?.minlength">パスワードは 6 文字以上で入力してください.</span>
  </div>
  <div>
    <label>名前: <input type="text" name="name" [formControl]="name" /></label>
    <span *ngIf="name.errors?.required">名前は必須です.</span>
    <span *ngIf="name.errors?.minlength">名前は 3 文字以上で入力してください.</span>
    <span *ngIf="name.errors?.maxlength">名前は 10 文字以内で入力してください.</span>
  </div>
  <div>
    <label>備考: <textarea name="memo" [formControl]="memo"></textarea></label>
    <span *ngIf="memo.errors?.maxlength">備考は 10 文字以内で入力してください.</span>
  </div>
  <div>
    <button type="submit" [disabled]="myForm.invalid">送信</button> 
  </div>
</form>
import { Component } from '@angular/core';
import {
  FormGroup,
  FormControl,
  FormBuilder,
  Validators
} from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  private title = 'app';

  private mail     = new FormControl('', [Validators.required, Validators.email]);
  private password = new FormControl('', [Validators.required, Validators.minLength(6)]);
  private name     = new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(10)]);
  private memo     = new FormControl('', [Validators.required, Validators.maxLength(6)]);

  private myForm = this.builder.group({
    mail     : this.mail,
    password : this.password,
    name     : this.name,
    memo     : this.memo
  });

  constructor(private builder: FormBuilder) {
  }

  show() {
    console.log(this.myForm.value);
  }
}

モデル駆動型のフォームにおいて, 重要となる, FromControl, FormGroup について解説します.

FormControl

FormControl は, コンポーネント内で, そのインスタンスを生成して利用します.

new FormControl(value [, validators])

第 1 引数は, フォームの初期値, 第 2 引数は, (必要であれば) 検証ルールを配列で指定します. 検証ルールには, Validators クラスを利用することができます.

  private mail     = new FormControl('', [Validators.required, Validators.email]);
  private password = new FormControl('', [Validators.required, Validators.minLength(6)]);
  private name     = new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(10)]);
  private memo     = new FormControl('', [Validators.required, Validators.maxLength(6)]);

FormGroup

FormGroup は, FormControl インスタンスを束ねる役割をもっています. これによって, フォーム全体でまとめて, エラーが発生していないかを検証することが可能になります.

FormGroup を利用するには, コンポーネントのコンストラクタで, FormBuilder 型の引数をうけとるようにします (ちなみに, これは依存性注入 (DI) と呼ばれるデザインパターンの 1 種です).

  constructor(private builder: FormBuilder) {
  }

FormControl インスタンスを束ねるには, FormBuilder インスタンスの group メソッドを利用します.

  private myForm = this.builder.group({
    mail     : this.mail,
    password : this.password,
    name     : this.name,
    memo     : this.memo
  });

テンプレートとのひもづけ

コンポーネント側で必要な実装ができれば, あとは, コンポーネントとテンプレートをひもづけるだけです. そのためには, formGroup ディレクティブと formControl ディレクティブを利用します.

<form [formGroup]="myForm" (ngSubmit)="show()">
  <div>
    <label>メールアドレス: <input type="email" name="mail" [formControl]="mail" /></label>
    <span *ngIf="mail.errors?.required">メールアドレスは必須です.</span>
    <span *ngIf="mail.errors?.email">メールアドレスを正しい形式で入力してください.</span>
  </div>
    <!-- ... -->
</form>