狛ログ

2020年6月30日火曜日

angularでの複数チェックボックスの取り扱い。


オフィス狛 技術部のHammarです。

最近久々にAngularプロジェクトを開発する機会があり、そこでまたいろいろと勉強になったことがあったので、ご存知の方には基本的なお話しなのかとは思いますが、今回は「angularで複数のチェックボックスを使った記述方法」の一例をご紹介できればと思います。

■作りたい画面

今回作る画面のイメージとしては、下記のような登録画面で、基本情報の入力と共にチェックボックスが複数設置され、その内容をフォーム送信して登録というようなシンプルな画面構造を想定します。

画面イメージ



■コンポーネント

まずコンポーネント側の記述ですが、今回フォーム送信を行うので、Angularのリアクティブフォームから、FormControl、FormGroup、FormBuilder、FormArrayクラスをそれぞれ利用します。 各クラスの細かな説明はAngularの公式ドキュメント内のリアクティブフォームの説明を見ていただいたほうがわかりやすいかもですが、めちゃくちゃざっくり書くとこんな感じかなと思います。
FormControl:単一入力欄の制御
FormGroup:FormControlのグループ化
FormBuilder:FormControlの作成に使う(FormControlをたくさん扱う場合にインスタンス不要)
FormArray:FormGroup内の配列要素コントロールの制御
さらに、今回は入力の必須チェックも行いたいので、Validatorsクラスもインポートします。

では、実際に上記をインポートし、コンポーネント側を記述します。
  1. export class AppComponent implements OnInit {
  2.   favoriteFruitsList: {key: string, value: string}[] = [
  3.     {key: '1', value: 'りんご'},
  4.     {key: '2', value: 'バナナ'},
  5.     {key: '3', value: 'みかん'},
  6.     {key: '4', value: 'ぶどう'},
  7.   ];
  8.  
  9.   checkBoxFormArray: FormArray;
  10.   groups: FormGroup[] = [];
  11.  
  12.   form: FormGroup = this.formBuilder.group(
  13.     {
  14.       name: ['', [Validators.required]],
  15.       age: ['', [Validators.required]],
  16.       tel: ['', [Validators.required]],
  17.       formFavoriteFruitsList: this.formBuilder.array(this.groups, [Validators.required, this.checkBoxValidator]),
  18.     }
  19.   );
  20.  
  21.   constructor(
  22.     private formBuilder: FormBuilder,
  23.     public v: UserValidator,
  24.   ) {}
  25.  
  26.   ngOnInit() {
  27.     this.v.formGroup = this.form;
  28.   }
  29.  
  30.   checkBoxValidator(control: FormArray) {
  31.     for (let i = 0, loop_cnt = control.length; i < loop_cnt; i++) {
  32.       if (control.at(i).value) {
  33.         return null;
  34.       }
  35.     }
  36.     return { unChecked: true };
  37.   }
  38.  
  39.   onChange(value, isChecked: boolean) {
  40.     this.checkBoxFormArray = this.form.get('formFavoriteFruitsList') as FormArray;
  41.     if (isChecked) {
  42.       this.checkBoxFormArray.push(new FormControl(value));
  43.     } else {
  44.       const index = this.checkBoxFormArray.controls.findIndex(x => x.value === value);
  45.       this.checkBoxFormArray.removeAt(index);
  46.     }
  47.   }
  48.  
  49.   onSubmit() {
  50.    // 登録処理
  51.   }
  52.  
  53. }

form: FormGroup = this.formBuilder.group(...
の記述で、まずはフォームの初期設定をおこないます。
ここでチェックボックスとなるformFavoriteFruitsListはarray()メソッドでフォーム配列とし、さらにその中でチェックボックスをグループ化しています。これはcheckBoxValidatorでチェックボックスの必須チェックの為にこのような記述にしておきます。
ちなみにここで作成したcheckBoxValidatorは、チェックボックスのいずれかにチェックが入っているかのバリデーションで、Validatorsクラスには無いオリジナルのバリデーションになります。
onChangeについてはhtml側の記載で説明します。

■html

  1. <form [formGroup]="form" (ngSubmit)="onSubmit()">
  2.   <h2>名前</h2><input type="text" formControlName="name">
  3.   <h2>年齢</h2><input type="text" formControlName="age">
  4.   <h2>電話</h2><input type="text" formControlName="tel">
  5.   <h2>好きな果物</h2>
  6.   <ng-container formArrayName="formFavoriteFruitsList">
  7.     <ng-container *ngFor="let item of favoriteFruitsList;">
  8.       <label>
  9.         <input type="checkbox" [value]="item.key" (change)="onChange(item.key, $event.target.checked)"><span>{{ item.value }}</span>
  10.       </label>
  11.     </ng-container>
  12.   </ng-container>
  13.   <button type="submit">登録</button>
  14. </form>

ここでformArrayNameにコンポーネント側で設定したformFavoriteFruitsListを記載します。
これによりこのチェックボックスは配列要素のフォームとして扱います。
またここで、コンポーネント側に作成した、チェックボックスをチェックした時に発火するonChangeを使います。
チェックをON、OFFする際に、checkBoxFormArrayの配列内で、値を追加、削除する関数になります。
これにより、submitした時に、実際にチェックした値だけフォーム送信されます。


今回記載した内容はあくまで一例で、結構他の記載方法もあるようで、もしかしたらもう少し簡単に書ける部分もあるかもしれません。
また画面的には非常にシンプルなのですが、まだAngularのリアクティブフォームの使い方をうまく把握できていないと最初は難しく、結構苦戦しました(苦笑)。

自分のようなAngular初心者の方で、もしご参考になればと思います。

2 件のコメント:

  1. とても参考になります。
    一つ質問なのですが、コード中の「UserValidator」は何者でしょうか??

    返信削除
  2. 「UserValidator」は独自に定義しているClassになります。

    Angularの公式サイト(https://angular.jp/guide/form-validation)を見ると、
    html側(hero-form-reactive.component.html)で、エラーがあったかどうか判定する時に、
    <div *ngIf="name.invalid && (name.dirty || name.touched)"
    と記載しているかと思います。

    こちら、項目が増えていくと、htmlがゴチャゴチャしてくるんですね。
    そこで、弊社のプロジェクトだと、
    name.invalid && (name.dirty || name.touched)
    をTypeScript側(hero-form-reactive.component.ts)に記載して、html側は「*ngIf="hasErrorName"」で済むようにしています。

    そうすると、今度は、TypeScript側の記載がかなり増えてきます。

    そこで、バリデーションの部分だけを全て切り出して、「UserValidator」というクラスを作っています。
    (もちろん、作るだけではダメなので、実際は、moduleなどに定義し、DI出来るようにしています。)

    「UserValidator」についての説明は、以上です。

    今回のブログの内容とは、直接関係無い部分なので、混乱させないように「UserValidator」は消しておけば良かったです。
    いずれ、上記についてもブログに記載しようと思います。

    返信削除