DRY up your forms
Angular is a great framework that offers some great tools. One of these tools is ReactiveForms
. In this post, I want to introduce you to an easier way to handle errors on reactive forms and avoid spaghetti code. I’ll jump straight to the implementation to save time.
What we’re going to build is a standard signup form, like the one shown below. I’ll be using Angular v9 and you can find the source code here.
As you can see, I have a very simple signup form here styled with angular material. The ts
code for the form is below — I’ve added some Validators
to get some errors:
I’ve seen so much code from different projects and one thing that hasn’t changed at all is error handling on forms. Let me show you what I mean:
For every single error that may occur you need to write another mat-error
tag, check if the error is present, and set an appropriate message for it. This has to be done repeatedly. When you have a single signup form that might not be an issue but if you have dozens of forms, you need to write the same code every time and it is tiring.
Let’s Think About It
We are programmers, not copy pasters. We must write beautiful and clean code. That’s why I decided to take a moment and think about it.
I’m kidding. Of course, it’s true we must write beautiful and clean code but that wasn’t why I decided to think about what could be done better. In fact, the moment I decided something had to change was when my team leader assigned me to implement error handling on more than 20 forms with an average of ten fields each and many error cases. The errors must appear below each field that the error is related to and server errors must be handled the same way too.
So, errors must appear like this:
I’ve begun to work on some global functions and achieved some success with them — but they’re also coupling my code on each component. I wanted something decoupled, something that can be copied and pasted in any other project and work like a charm. So I ended up creating an injectable class and called it ErrorHandler
(very creative name).
You can check out the class here.
It will take a long time to dive into what’s inside this class, so I’ll talk only about how you can easily use it, as I said, with only three lines of code. Anyway, I’ll be happy if anyone has any ideas to improve it — just contact me. My goal is to work on it a little more and transform it into an npm package.
The Main Idea
The idea behind this class is that for every form we have we also create an object of errors. We take all control names from the form and assign them as keys to error object and the error message of each form control assign as value to those keys.
If that hasn’t made it clear enough I think the code below will:
Implementation
Take the code from my gist and create a file somewhere in your project and paste it. It doesn’t matter where the file is located. You only need Reactive Forms to be imported in your module.
Import and inject it into your component’s constructor, like this:
Create an empty object to hold errors inside your component:
Call handleErrors()
method from the class inside onInit()
but after you initialize your form:
The handleError()
method takes two arguments — the first one is your form and the second one is a local empty object to keep errors.
Now go to your template and write only a single mat-error tag like this for each formControl
:
So, inside the mat-error
, this is the only thing you need to write:
<mat-error>{{errors.theFormControlName}}</mat-error>
Now you’re not writing repeated code all over the app and the errors are visible below the field that has the error — great!
Also, there’s a method called organizeServerErrors
to deal with validation errors sent from the server, this method is written explicitly to work with the way that my backend with Django Rest sents me errors. So if you are going to use it you need to work around some to change in your backend’s error format.
Anyway, it is enough to call setErrors()
on the required form control and add the error type into the error cases in the class, as shown below:
// where your error comes from the server
this.signUpForm.get('email').setErrors({emailInUse: true});
// error.handler.ts
...
} else if (errors.pattern) {
this.message = 'Invalid value';
} else if (errors.passwordMismatch) {
this.message = 'Passwords do not match';
} else if (errors.emailInUse) {
this.message = 'There is an account with that email';
} else {
this.message = '';
}
Conclusion
We’re all bored of writing the same things over and over again. This class offers a central solution to error handling in Angular ReactiveForms.
Currently, I’m working on the implementation to handle the form arrays that hold other form groups inside them too. The goal is simple: to have one method call to handle all errors.
If you want to work with me or give any suggestions on the class code I will be very happy to hear from you!
Thanks for reading!
Top comments (1)
public handleErrors(form: FormGroup, errorObject: any) {
this.form = form;
this.errorObject = errorObject;
form.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
).subscribe(() => {
if (form.invalid) {
this.findErrors(form.controls);
}
});
}
tadam! and we have subscriptions that will live forever