AngularのReactive Formsで複数チェックボックスを制御

AngularのReactive Formsで複数チェックボックスを制御

AngularのReactive Formsを利用して、複数チェックボックスがある場合にどれか一つでもチェックがされていない場合にエラーを表示する方法を解説します。

Reactive Forms とTemplate-driven forms

まず最初にReactive Formsの簡単な説明から。

Angularではform制御の方法として「Template-driven forms」と「Reactive Forms」が用意されています。

Template-driven formsは ngModelディレクティブを利用してフォームとモデルを直接紐付け、双方向データバインディングを行う方法です。

以下のサンプルではTemplate-driven formsで入力内容をページ内に表示をしています。

@Component({
  selector: 'my-app',
  template: `
    <div>
      input: <input type="text" [(ngModel)]="myText"><br>
      output: {{myText}}
    </div>
  `,
})
export class App {
  public myText = "hello";
}

Reactive FormsはFormControlを作成してフォームの値を管理します。

以下のサンプルではReactive Formsで入力内容をページ内に表示をしています。

@Component({
  selector: 'my-app',
  template: `
    <div>
      input: <input type="text" [formControl]="myText"><br>
      output: {{myText.value}}
    </div>
  `,
})
export class App {
  public myText = new FormControl();
}

どちらを使っても良いですが、Reactive FormsはFormGroupやFormArrayといった複数の入力項目をまとめる機能があるため、入力画面などの複数の入力項目があるような画面での利用に適しています。

Reactive Formsのバリデーション

Reactive Formsではバリデーション用の機能が用意されており、必須チェックや文字数のチェックが簡単に行うことができます。(Template-driven formsでも用意されております)

Validatorsクラスを読み込み、new FormControl()の第2引数で配列でバリデーションルールを指定していきます。

以下のサンプルでは必須、10文字以内のバリデーションを行いエラーがある場合はエラー内容をページ内に表示しています。

@Component({
  selector: 'my-app',
  template: `
    <div>
      input: <input type="text" [formControl]="myText">
      <strong *ngIf="myText.touched && myText.errors.required">
        必須
      </strong>
      <strong *ngIf="myText.touched && myText.errors.minlength">
        4文字以内
      </strong>
      <br>
      output: {{myText.value}}
    </div>
  `,
})
export class App {
  public myText = new FormControl('',[
    Validators.required,
    Validators.maxLength(10),
  ]);
}

myText.touchedは一度でもそのUIに触れられたかどうかの状態をもっており、初期表示時にいきなりエラーが表示されないように利用されます。

Reactive Formsのカスタムバリデーション

そろそろ本題に。

必須や最大何文字ようにバリデーションルールが用意されているものはよいのですが、「複数チェックボックスのうちどれか一つがチェックされていること」といったバリデーションはデフォルトでは用意されていません。

そういった場合は独自のバリデーションルールをカスタムバリデーションとして定義することができます。また複数のチェックボックと言った複数のUIの制御を行いたい場合はFormGroupもしくはFormArrayでUIのまとまりを作成します。

まずは動作サンプルとコードを確認ください。

@Component({
  selector: 'my-app',
  template: `
    <div>
      <form
        [formGroup]="formGroup"
        (ngSubmit)="sumbit()"
        *ngIf="!sumbitSuccess"
      >
        <p>一つ選択してください</p>
        <div>
          <ng-container
            *ngFor="let checkbox of formGroup.controls.checkboxs.controls;let i = index"
          >
            <input type="checkbox" [formControl]="checkbox"> {{checkboxsItem[i]}}
          </ng-container>
        </div>
        <p
          *ngIf="formGroup.controls.checkboxs.touched && formGroup.invalid"
          style="color:red"
        >なにか選択してください</p>
        <div>
          <input type="submit" value="送信" #btn>
        </div>
      </form>
      <p *ngIf="sumbitSuccess" style="color:green">送信送信成功</p>
    </div>
  `,
})
export class App {
  public formGroup: FormGroup;
  public checkboxs: FormArray;
  public checkboxsItem = [
    '選択A' ,
    '選択B' ,
    '選択C' ,
    '選択D' ,
    '選択E' ,
  ];
  public sumbitSuccess = false;
  ngOnInit() {
    const checkboxs = this.checkboxsItem.map(reason => {
      return new FormControl(false);
    });
  
    this.formGroup = new FormGroup({
      checkboxs: new FormArray(checkboxs, this.multipleCheckbox),
    });
  }
   
  /**
   * 複数チェックボックスの必須チェックのカスタムバリデータ
   * @param { FromArray } fa チェックボックス
   */
  multipleCheckbox(fa: FormArray) {
    let valid = false;
    for (let i = 0; i < fa.length; i++) {
      if (fa.at(i).value) {
        valid = true;
        break;
      }
    }
    return valid ? null : {
      multipleCheckbox: true
    };
  }
   
  /**
   * 送信処理
   */
  sumbit(){
    // チェックボックスをtouchedに変更
    this.formGroup.controls.checkboxs.markAsTouched();
    // エラー時には送信しない
    if(this.formGroup.invalid){
      return;
    }
    // 送信処理、サンプルではあくまで見た目の切り替えのみ 
    this.sumbitSuccess= true;
  }
}

ポイントの1つ目はFormArrayを利用してチェックボックスのグルーピングを行っていること。

ngOnInit() {
    const checkboxs = this.checkboxsItem.map(reason => {
      return new FormControl(false);
    });
    
    this.formGroup = new FormGroup({
      checkboxs: new FormArray(checkboxs, this.multipleCheckbox),
    });
}

checkboxsでは複数チェックボックの状態を管理することができます。

もう一つのポイントはmultipleCheckboxというカスタムバリデーションを複数チェックボックに対して適応していること。multipleCheckboxではチェックボックスを精査して1つでもチェックがあるかを確認してある場合のみエラーを出力しています。

multipleCheckbox(fa: FormArray) {
    let valid = false;
    for (let i = 0; i < fa.length; i++) {
      if (fa.at(i).value) {
        valid = true;
        break;
      }
    }
    return valid ? null : {
      multipleCheckbox: true
    };
}

このように、カスタムバリデーションを利用することでReactive Formsで様々なことができるようになります。

また、送信ボタンが押された際にmarkAsTouched();を実行してUIを一度でもタッチされた状態に変更しています。

/**
 * 送信処理
 */
sumbit(){
    // チェックボックスをtouchedに変更
    this.formGroup.controls.checkboxs.markAsTouched();
    // エラー時には送信しない
    if(this.formGroup.invalid){
      return;
    }
    // 送信処理、サンプルではあくまで見た目の切り替えのみ 
    this.sumbitSuccess= true;
}

関連エントリー

Angularによるスライドアニメーション
続:AngularでChangeDetectionによるパフォーマンスチューニング
AngularでChangeDetectionによるパフォーマンスチューニング
SystemJSでAngular 2の環境を構築する

スポンサードリンク

«「秋のJavaScript祭 in mixi 2017」で「RxJSで始めるリアクティブプログラミング」というお話をしてきました。 | メイン | プロジェクトによりnodenvとndenvを切り替える»