Translation validation for Angular - automatic validation messages translated
Tags: angular, typescript, showdev
Published: 2021-07-13 07:19:04

> Automatic validation messages for Angular forms in any language

## tl;dr
A library that can automatically display validation messages in any language. Check out the [library here](https://github.com/RiskChallenger/translation-validation) or the [example here](https://riskchallenger.github.io/translation-validation/) or if you want to have a look at the example code checkout the [Stackblitz](https://stackblitz.com/edit/ngx-translation-validation).

```html
<label>Email:
  <input formControlName="email">
</label>
<div *ngIf="myForm.controls['email'].invalid">
  <div *ngIf="myForm.controls['email'].errors.required">
    {{ translate('email.require') }}
  </div>
</div>
```
Writing this code for one input can be okay. It is very clear and you know exactly what it does. However, lets say we also want to check if the email address is valid and that we have 10 other inputs that need validation. In that case this code will become very tedious and is cumbersome to write. Therefore we created `ngx-translation-validation`. With this package you can write the following.

```html
<label>Email:
  <input formControlName="email">
</label>
```
The package will automatically take care of any form validation that is enabled on this form. You no longer have to write any html to display the errors. 

## Reasons for development
While developing RiskChallenger, an application for risk management, we had the need to show validation errors to the user. We have forms to setup the risk file, enter new risks, measures and all of those have quite a few inputs. As a developer should be, lazy, I did not want to write too much code and neither did I want to reinvent the wheel. So I googled on how to do this properly. At the time of writing I found the following solutions:

 - [Valdemort](https://ngx-valdemort.ninja-squad.com/documentation/index.html)
 - [ngx-errors](https://github.com/ultimatecourses/ngx-errors)
 - [Tutorials like these](https://www.pluralsight.com/guides/how-to-display-validation-messages-using-angular)

All of these examples have one thing in common; they require boilerplate code. While I'd say that it should not be needed. We can create a directive that binds to the formControl and when the formControl is invalid then it can show the error message. Even better, it can render the error message using a translation library like [transloco](https://ngneat.github.io/transloco/) or [ngx-translate](http://www.ngx-translate.com/). 
 > At the time of writing it does only support Transloco and not ngx-translate, but I plan to add this. Also other translation libraries should be easy to add.

Based on the form name and the form control name it can easily determine which validation message should be rendered to the user. 

So what does the library need to do?
 - Check if a form control is invalid
 - Generate error component with error message
 - Generate different messages for different forms
 - Show errors on submit

Without going into too much depth I will try to explain how the library does its job. I will do this by showing some code snippets and explain how and why it works.

## Detect form control validity, no more boilerplate
To prevent the boilerplate code as shown below we need to check the validity of every form control and when invalid inject a component with a validation message.
```html
<div *ngIf="myForm.controls['email'].errors.required">
  {{ translate('email.require') }}
</div>
```
We create a directive to do this and add the selector `formControl` and `formControlName`. This way the directive is automatically applied to all inputs having one of those. Inside the directive we subscribe to the `statusChanges` of the `formControl`.  The `statusChanges` is 

> A multicasting observable that emits an event every time the
> validation `status` of the control recalculates.

according to the Angular documentation. The validation `status` of the control is recalculated according the `updateOn` property of the `AbstractControlOptions`.

```typescript
constructor(@Self controlDir: NgControl){}
...
this.controlDir.statusChanges.subscribe(  
  () => {  
    const controlErrors = this.controlDir.errors;  
    if (controlErrors) {  
      const firstKey = Object.keys(controlErrors)[0];  
	  this.setError(`${this.config.type}.${this.controlDir.name}.${firstKey}`);  
    } else if (this.ref) {  
      this.setError(null);  
    }  
  }  
);
```
When the `status` of a control changes we check if the control is valid. If there is an error we will call the `setError` method with the translation key for the first error on the control. This function should create a component and display the error message. The  translation key is formatted like this `type`.`controlName`.`errorKey`. 

 - `type` is the type of validation, which defaults to `validation` just to set it apart from any other translation strings
 - `controlName` is the name of the form control
 - `errorKey` is the name of the validation constraint

For example `validation.name.required` is the translation key for the `name` form control that has `Validation.required` as constraint.

The component containing the error message will be created and the message is then set.
```typescript
const factory = this.resolver.resolveComponentFactory(this.config.errorsComponent);  
this.ref = this.container.createComponent(factory);
this.ref.instance.text = errorText;
```
Then the component that is generated has the following template.
```html
<ng-container *transloco="let t">  
  <p class="input-errors">  
    {{ t(text) }}  
  </p>  
</ng-container>
```
The text set with `this.ref.instance.text` is an `@Input() text` on the component after which the error text can be used in the template. The error text is an translation string and so we use it inside the transloco translation function.

## Form name as scope to differentiate messages between forms
Let's say you have 2 forms that have an input for name, but the names have different meanings. In this case you might want a different validation message.  In which case `validation.name.required` probably won't do it, because `name` will the a form control of many forms (eg. project name, user name, company name). You probably want something like `validation.userForm.name.required`. To achieve this we must be able to set the scope for a form or a group of form controls.
```html
<form [formGroup]="form" ngxTvScope="userForm">
  <div>  
    <label for="name">{{ translate('name') }}</label>  
    <input formControlName="name" id="name" />  
  </div>
</form>
```
`ngxTvScope` is a directive with the sole purpose of relaying the scope to generate the correct translation string. The translation string then becomes `type`.`scope`.`controlName`.`errorKey`.

To get the scope inside our error directive we simply inject the scope directive. It will inject the closest scope directive, searching in its parent nodes. Since providing a scope is not mandatory we do make it optional and should consider a default option. Also notice the `@Host()` which limits the search up to the host element, which means that it searches inside the component where the form control lives [angular docs](https://angular.io/guide/dependency-injection-in-action#optional).
```typescript
constructor(
	@Optional() @Host() controlScope?: NgxTvScopeDirective
){}
...
this.scope = this.controlScope?.scope ?? this.config.defaultScope;
...
this.setError(`${this.config.type}.${this.scope}.${this.controlDir.name}.${firstKey}`);
```

## Result
We now get validation messages when a form control is at an invalid state... automatically! All you need to do is install the package, import it in your `AppModule`, import it in the module your component lives in and then all your form elements will show validation messages.  Also check out the [example here](https://riskchallenger.github.io/translation-validation/) or if you want to have a look at the code checkout the [Stackblitz](https://stackblitz.com/edit/ngx-translation-validation).

## What's more?
These were some interesting example of what this library can do, but of course there is more to it than just this. You can checkout the project on [GitHub](https://github.com/RiskChallenger/translation-validation). Please give it a try, the readme should provide a pretty decent getting started. If you feel that some features are missing or that there is a bug, please do not hesitate to create on issue or create a PR, I welcome contributions. ;-)

## RiskChallenger
RiskChallenger is a company that delivers intuitive risk management software that encourages users for collaborate to explore risks and mitigate them by creating and managing measure. These risks can be project risks, for instance for large construction projects, or business risks. Want to know more about our company or our software? Please have a look at our website [https://riskchallenger.nl](https://riskchallenger.nl)