# A new way to validate Angular Forms https://timdeschryver.dev/blog/a-new-way-to-validate-angular-forms Over the past year, our team has created a lot of different forms for a form-heavy application. One of the difficulties we experiences was validating fields that are dependent on other fields of the form. For example, conditional validation or validation where multiple fields are needed to validate a control. > この1年間、私たちのチームは、フォームを多用するアプリケーションのために多くの異なるフォームを作成してきました。その中で苦労したのが、フォームの他のフィールドに依存しているフィールドのバリデーションです。例えば、条件付きのバリデーションや、1つのコントロールを検証するために複数のフィールドが必要なバリデーションなどです。 Now, when we look back at those forms, we notice that most of the forms are build differently. We're not the only ones that struggle with this. I asked the question of how to implement a conditional validation in Angular Forms on Twitter, and they were multiple answers on how to tackle this problem. All of them were different but there was a consensus, it isn't always straightforward, nor is it easy. > さて、それらのフォームを振り返ってみると、ほとんどのフォームが違った形で作られていることに気がつきます。これに悩んでいるのは私たちだけではありません。TwitterでAngular Formsに条件付きバリデーションを実装する方法を質問したところ、この問題への取り組み方について複数の回答がありました。どれも違っていましたが、共通していたのは、必ずしも一筋縄ではいかないし、簡単でもないということです。 To streamline our forms and to make the validation easier I started a proof of concept to validate forms differently. > フォームを合理化し、バリデーションを容易にするために、私はフォームのバリデーション方法を変えるコンセプトの検証を始めました。 ## Goals The goal is to come up with a solution for more complex forms, but also that it can be used for the simple forms. > 目標は、より複雑なフォームのためのソリューションを考え出すことですが、シンプルなフォームにも使用できることです。 As a developer using this new validation API, I don't want to write a lot of code that introduces subtle bugs. Instead, I just want to declare validation rules. > この新しいバリデーションAPIを使う開発者としては、微妙なバグを引き起こすようなコードをたくさん書きたくありません。その代わり、バリデーションルールを宣言するだけにしたいのです。 These validation rules must be reusable (and testable). We get extra points if common rules are built-in, preferably similar to the Angular validators. > これらの検証ルールは再利用可能(かつテスト可能)でなければなりません。できればAngularのバリデータのように、一般的なルールが組み込まれていれば、さらにポイントが高くなります。 I also don't want the solution to be destructive because I don't want to rewrite existing forms. Therefore, the solution has to build a layer on top of the existing Angular Forms API. > また、既存のフォームを書き換えたくないので、ソリューションが破壊的なものになることも避けたいです。したがって、このソリューションは、既存のAngular Forms APIの上にレイヤーを構築する必要があります。 ## The proposal The Angular Forms API already provides the basic building blocks to make these goals possible. An `AbstractControl` has the method `setError`, which is all that's needed to make a form (control) valid or invalid. Based on the validity of these form controls, the form derives its validity status. > Angular Forms APIは、これらの目標を実現するための基本的な構成要素をすでに提供しています。`AbstractControl` には `setError` というメソッドがあり、これだけでフォーム(コントロール)の有効・無効を決めることができます。これらのフォームコントロールの有効性に基づいて、フォームはその有効性ステータスを導き出します。 For asynchronous validators, we'll also need `markAsPending` to set the control's status to `pending` while a validator is validating its value. > 非同期バリデーターの場合、バリデーターが値を検証している間、コントロールのステータスを`pending`にするための`markAsPending`も必要になります。 The intention of this new validation layer is only to validate the form value and to make use of the existing Forms API to set and clear the errors of the form controls, and thus also for the form itself. > この新しいバリデーションレイヤーの目的は、フォームの値を検証することと、既存のForms APIを利用して、フォームコントロールのエラーを設定したり解除したりすること、ひいてはフォーム自体も検証することにあります。 ![](https://timdeschryver.dev/blog/a-new-way-to-validate-angular-forms/images/validate.png "") (On a value change of the form, the validation is triggered and errors are set on the control when needed) > フォームの値が変更されるとバリデーションが行われ、必要に応じてコントロールにエラーが設定されます。 To build the validation layer, we use the `createValidator<T>()` wrapper and assign validation rules to the form properties. A validation rule consists of the details of why the validation has failed. The validation rule also contains a message for a user and can include these details in the message. > バリデーションレイヤーを構築するために、`createValidator<T>()`というラッパーを使い、フォームのプロパティにバリデーションルールを割り当てます。バリデーションルールは、バリデーションが失敗した理由の詳細で構成されます。バリデーションルールにはユーザーへのメッセージも含まれており、これらの詳細をメッセージに含めることができます。 To active the validator, it must be subscribed to, and the validator returns an Observable containing all of the messages. > バリデータをアクティブにするには、バリデータを購読する必要があります。バリデータは、すべてのメッセージを含むObservableを返します。 To see this in action, please have a look at the following examples. > これを実際に見るには、次の例を見てください。 https://stackblitz.com/edit/create-validator-poc ## The differences Let's highlight the differences and the benefits compared to the default behavior by comparing the two. > 両者を比較することで、デフォルトの動作との違いやメリットを浮き彫りにしましょう。 ### Conditional Validation To disable and enable form validation based on a control's value, we use `when`. For example, in the validator below `name` becomes required when `strict` is true. > コントロールの値に基づいてフォームバリデーションを無効にしたり有効にしたりするには、`when`を使います。たとえば、以下のバリデータでは、`strict`がtrueのときに`name`が必須になります。 ```typescript formValidator = createValidator<FormValue>(this.form, { name: { validator: required(), when: (_, form) => form.strict, }, }) ``` Without the `createValidator` wrapper, we need to juggle with validators by adding or removing validators of the control. If you use the Taiga UI kit, you can use the `tuiValidator` directive. > `createValidator`ラッパーがないと、コントロールのバリデータを追加したり削除したりして、バリデータをやりくりする必要があります。Taiga UI キットを使用している場合は、`tuiValidator` ディレクティブを使用できます。 ```typescript this.form.get('strict')!.valueChanges.subscribe((strict) => { if (strict) { this.form.get('string')!.setValidators(Validators.required) this.form.get('string')!.updateValueAndValidity() } else { this.form.get('string')!.setValidators(null) this.form.get('string')!.updateValueAndValidity() } }) ``` Doing this can quickly become bloated and hazardous for large forms when there's cohesion between different controls. In our case, we overruled a previously validator with a different one. It was after a couple of debugging sessions that we were able to reproduce this hidden bug. With the `when` syntax, it becomes easier to see the coherence between (multiple) controls and (multiple) validators. > これを行うと、すぐに肥大化してしまい、異なるコントロールの間にまとまりがあるような大きなフォームでは危険です。今回のケースでは、以前のバリデータを別のバリデータで上書きしました。この隠れたバグを再現することができたのは、数回のデバッグセッションの後でした。 `when`構文を使えば、(複数の)コントロールと(複数の)バリデーターの間のまとまりを簡単に見ることができます。 ### Access to the form value In each validator, we have access to the value of the whole form. Having access to the form value is useful when you need to compare properties of a form (e.g. with the `equal` rule), or when the validation is based on multiple form values. In the snippet below, `passwordConfirmation` needs to be equal to the value of `password`. > 各バリデータでは、フォーム全体の値にアクセスすることができます。フォームの値にアクセスできることは、フォームのプロパティを比較する必要がある場合 (例えば `equal` ルールを使用する場合)や、複数のフォームの値に基づいてバリデーションを行う場合に便利です。以下の例では、`passwordConfirmation` が `password` の値と等しい必要があります。 ``` formValidator = createValidator<User>(this.form, { password: [required(), minLength(7)], passwordConfirmation: equal((user) => user.password), }) ``` To implement this scenario with the Forms API, we have two options. > このシナリオをForms APIで実装するには、2つの選択肢があります。 One, we can group both controls and create a validator for the form group. This has the disadvantage that the validation message isn't bound to a specific control. For us, this was problematic because our generic controls expect the validation messages to be bound to the control, not to a group, to display a validation message to the user. > ひとつは、両方のコントロールをグループ化して、 フォームグループ用のバリデータを作成することです。これには、バリデーションメッセージが特定のコントロールにバインドされないというデメリットがあります。私たちの汎用コントロールは、ユーザーにバリデーションメッセージを表示するために、グループではなくコントロールにメッセージがバインドされることを期待しているので、これは問題でした。 The second option is to re-create the validators when the value changes. > 2つ目の方法は、値が変更されたときにバリデータを再作成することです。 ```typescript this.form.get('password')!.valueChanges.subscribe((password) => { this.form.get('passwordConfirmation')!.setValidators(CustomValidators.equal(password)) this.form.get('passwordConfirmation')!.updateValueAndValidity() } }) ``` Just like Conditional Validation this becomes tricky for bigger forms. > 条件付き検証と同様に、これは大きなフォームでは厄介になります。 ### Statically Typed When you pass a type to `createValidator<T>`, the rest of the validation is statically typed. > `createValidator<T>` に型を渡すと、残りのバリデーションは静的に型付けされます。 While building the validator, properties of the form model are auto-completed and the value of a form control and the form will be typed in all of the validator rules. The built-in validators are also typed so we can't make the mistake of using the wrong validator. For example, you won't be able to use a number validation (e.g. `greaterThan`) for a property that holds a string value. > バリデータを構築している間、フォームモデルのプロパティは自動補完され、フォームコントロールとフォームの値はすべてのバリデータルールで型付けされます。組み込みのバリデータも型付けされているので、間違ったバリデータを使用するというミスを犯すことはありません。たとえば、文字列の値を保持するプロパティに数値のバリデーション (例: `greaterThan`) を使用することはできません。 ### Main difference To make the above use cases possible, the whole form is validated on every change. This impact is negligible for synchronous validators because these are just methods that are invoked, thus should be fast and has a low impact on the performance. This is a different story for asynchronous validators, where this behavior might have an impact. > 上記のユースケースを実現するために、変更のたびにフォーム全体のバリデーションを行います。同期バリデータの場合、この影響は無視できます。なぜなら、これらは単に呼び出されるメソッドであり、高速で、パフォーマンスへの影響も少ないはずだからです。しかし、非同期バリデータの場合は話が違います。この振る舞いは影響を与えるかもしれません。 ## Impact As a developer, the `createValidator` wrapper intends to make it easier to write and read the validation logic of your Angular forms. Its minimal API (with the common supplied validation rules) should also allow a quick transition towards the `createValidator` wrapper. Refactoring existing forms will have a minimal impact because the status and the value of the form remain the same as before. > 開発者としては、`createValidator` ラッパーは Angular フォームのバリデーションロジックを簡単に書いたり読んだりできるようにすることを目的としています。最小限の API(一般的に提供されている検証ルール)により、`createValidator` ラッパーへの素早い移行が可能になります。既存のフォームをリファクタリングしても、フォームのステータスや値は以前と変わらないので、影響は最小限に抑えられます。 ## Final thoughts While this is just a simple proof of concept, I can see the benefits of adding an extra layer on top of the Angular Forms API and use the basis as building blocks. Because of this, and because of the common validation rules that behave the same as the built-in Angular validator I expect it to be a small and quick shift if we decide to pull the trigger. > これは簡単なコンセプトの証明に過ぎませんが、Angular Forms APIの上にレイヤーを追加し、その基礎を構成要素として使用することの利点がわかります。これと、Angularに組み込まれたバリデータと同じ動作をする汎用バリデーションルールがあるので、もし実行することになっても、小さくて迅速な移行になると思います。 The form validation is more expressive, and this should help with the development while also improving the maintenance cost. > フォームのバリデーションがより表現力のあるものになっているので、開発に役立つと同時に、メンテナンスコストの改善にもつながるはずです。 As always, feel free to let me know your thoughts.