Skip to content

Handling Validation Errors

In the last lesson, we discussed validators. Naturally, a failed validator means there is something wrong, and if there is something wrong we are going to need to inform the user what specifically is wrong.

This seems like a good time to tell a little story about a problem my wife just encountered with an online form. Having filled out a large form, it would not submit indicating that there was some error with one of the fields. Only, the form didn’t say which field had the problem. She emailed the company for help, and they emailed her a list of potential validation issues with different fields. This is probably the most absurd example I have heard of, but if something does go wrong, we want to clearly indicate to the user what has gone wrong and where so that they can fix it. We certainly don’t want to be emailing our users the validation requirements for a form.

By default, if one of our validators fails, nothing obvious happens. Let’s say that we prevent our form from being submitted if there are validation errors:

<form [formGroup]="myForm" (ngSubmit)="handleSubmit()">
<div>
<input formControlName="username" type="text" />
<span>{{ usernameStatus() }}</span>
</div>
<div>
<input formControlName="age" type="number" />
</div>
<button type="submit" [disabled]="myForm.valid">Submit</button>
</form>

The user will be able to see that the submit button is disabled, but they won’t know why. We could quite easily add a message like this:

<form [formGroup]="myForm" (ngSubmit)="handleSubmit()">
<div>
<input formControlName="username" type="text" />
<span>
{{ status() }}
</span>
</div>
<div>
<input formControlName="age" type="number" />
</div>
@if(!myForm.valid){
<p>There are errors with the form!</p>
}
<button type="submit" [disabled]="myForm.valid">Submit</button>
</form>

Now the user will know that there was some validation issue with the form that they need to fix, but they still don’t know what specifically the issue was. Dealing with this issue is not trivial, and the larger our form becomes the more difficult it becomes to manage unless we are using some sort of scalable solution.

The Obvious Way

The easiest thing we can do is add an @if that checks if the field is valid and displays an error if it is not:

<form [formGroup]="myForm" (ngSubmit)="handleSubmit()" #form="ngForm">
<div>
<input formControlName="username" type="text" />
<span>{{ usernameStatus() }}</span>
@if(!myForm.controls.username.valid){
<p>Please provide a username that is not taken</p>
}
</div>
<div>
<input formControlName="age" type="number" />
@if(!myForm.controls.age.valid ){
<p>Age must be greater than 18</p>
}
</div>
<div>
<input formControlName="password" type="password" />
@if(!myForm.controls.password.valid){
<p>Password must be at least 8 characters long</p>
}
</div>
<div>
<input formControlName="confirmPassword" type="password" />
@if(myForm.hasError('passwordMatch')){
<p>Must match password</p>
}
</div>
@if(!myForm.valid){
<p>There are errors with the form!</p>
}
<button type="submit" [disabled]="myForm.pending">Submit</button>
</form>

We will talk about this a little later, but you can probably see how this solution gets a bit messy for anything more than a very simple form.

Hide Validator Errors Until Interaction

One annoying thing about our current set up is that all of the error messages are displayed immediately, before the user has even had the chance to enter anything. Using another property of forms, we can wait until the user supplies a value before we display any validation errors.

All we need to do, is add an additional check for dirty before displaying the message:

@if(!myForm.controls.age.valid && myForm.controls.age.dirty){
<p>Age must be greater than 18</p>
}

Now the error won’t display until the user actually interacts with the field and enters an invalid value.

Show All Validation Errors on Submit

The example above is nice because the user does not get immediately bombarded with error messages, but the downside is that if the user tries to submit the form there might be validation errors that they can not see. If the errors only display once the control is interacted with, a user might skip a control entirely and not realise that there is an error associated with it.

What we can do to help with this situation is to display all validation error messages once the user clicks the Submit button. To do this, we are going to make use of that ngForm trick we had a look at in the lesson on the FormGroupDirective.

If we set up a template variable that is a reference to the FormGroupDirective attached to our form:

<form [formGroup]="myForm" (ngSubmit)="handleSubmit()" #form="ngForm">

We can then also check if the form has been submitted yet in our @if statements. We are going to modify all of our errors to work like this instead:

<div>
<input formControlName="age" type="number" />
@if(
!myForm.controls.age.valid &&
(myForm.controls.age.dirty || form.submitted)){
<p>Age must be greater than 18</p>
}
</div>

Now if there is a validation error we are going to display it when either of these conditions are met:

  • The control has been interacted with (e.g. it is dirty)
  • The form has been submitted

Advanced Validation Error Handling

The method we have outlined in this lesson is easy enough to implement for small-ish forms, but it has its downsides. One is that we need to write out a bit of boilerplate for each control, and another is that our errors are not specific. We are not displaying error messages dynamically based on what the actual error is. For example, if we have a control with the following validations:

  • required
  • min(10)
  • max(50)

We would need to make sure we describe all of these potential errors in our message, e.g:

Please make sure that this field is greater than 10 and less than 50

We don’t know which validations failed so we need to make sure we cover all of them. However, the information regarding which validation failed is available to us and we can use it.

Although we could build our own custom component or directive for handling displaying validation errors, if your forms are more complex I would recommend using a pre-existing library. This is a problem that has been around for a long time and people have made ready-to-go solutions.

Perhaps the most popular library for forms (this is just based on my own perception) is ngx-formly which is a totally different approach to building forms that is built on top of Angular’s Reactive Forms. If you are going to be building complex forms, then it will likely be well worth your effort to learn a library like this.