リアクティブフォームのバリデーションを管理する良い方法が知りたいです

フォームにデータを入力しバックエンドのAPIに登録するアプリケーションを作成しています。
この際に条件に応じてバリデーションを変化させる必要があるのですが、現在
switch(条件) {
case 条件1:
formControl.clearValidators()
formControl.clearAsyncValidators()
formControl.setValidators(Validators.required())
formControl.updateValueAndValidity()
case 条件2:…
}
のようなコードになっていて、

①バリデーションをセットしたりクリアしたりしていて、現在どういう状態になっているか分かりづらくバグが怖い
②条件が増えたりフォームの入力項目が増えるたびに、全てのswitch文に対してバリデーションをセットしたりクリアする必要があり影響範囲が大きくやはりバグが怖い

と懸念があります。
そこでリアクティブフォームのバリデーションについて管理する良い方法があれば教えていただきたいです。

1 Like

質問されている内容はリアクティブフォームというよりもAngularに関係ないプログラミングの問題のように感じます。つまり手続き的なコードをどのように宣言的に書くかということですが、様々なアプローチがあるとは思います。たとえばバリデーターを作る部分を関数に切り出す( formControl.setValidators(getValidatorsForCase1()) のような)とか。アプリケーションに対して副作用のない関数に切り出すとテストしやすくなり、ユニットテストでバグがないことを担保できるかと思います。

また別の話として、clearValidators を呼んでも setValidators で結局バリデータ設定は set (上書き)されるので、 clearしなくても大丈夫です。

他の部分のコードとどのように接しているかによって変わると思いますが、今ある情報だけで改善するなら

  ... {
    formControl.setValidators(getValidators(条件));
    formControl.updateValueAndValidity();
  }

function getValidators(条件) {
  switch(条件) {
    case 条件1:
      return [Validators.required()];
    case 条件2:…
  }
}

というのがいったんテストするのに苦労しない形かと思います。

2 Likes

ご返信ありがとうございます。
指摘していただいてなるほどそうだなと思ったのですが、おっしゃる通り「Angularやリアクティブフォームの問題ではなく、手続き的なコードをどのように宣言的に書くか」の問題でした。
非常に参考になりました、ありがとうございます。

提示して頂いたコードはシンプルなTypeScriptで表現されていますが、コスト等考慮してこれをngrxで管理するほど価値はあるでしょうか?
具体的には以下のようなコードです。

export interface State {
  formItem1: ValidatorFn
}

export const initialState: State {
 formItem1: Validators.required
}

export function(state = initialState, action = Action): State {
 switch(action.type) {
 case FormValidationActionTypes.formItem1:
   return Object.assign({}, {...state, formItem1: Validators.email)}
}
}

switch文の判定対象となる 条件 の部分を状態として管理することはあるかもしれませんが、バリデータそのものをNgRxで管理するのは避けたほうがよいと思います。
バリデータは関数なので、JSONとしてシリアライズできないオブジェクトです。Reduxの原則としてステートはJSON文字列にシリアライズ可能であることが必要ですので、バリデータそのものを状態に含めるのは悪手です(ひょっとするとエラーになるかもしれません)

2 Likes

再度のご返信ありがとうございます。
ReduxにおいてJSON文字列にシリアライズ可能でなければならないのは知りませんでした。この点も教えてくださりありがとうございます。