Reducing the forms boilerplate — make your Angular forms reusable
Reduce the forms boilerplate by creating reusable custom forms controls with the ControlValueAccessor interface. Learn how to create reusable forms, subforms and how to leverage the DI to make everything much cleaner!
At the end of this blog, you can find a Stackblitz project with all the examples.
One of the most powerful packages in Angular is definitely the Forms package. I think every developer who created forms without Angular will admire the simplicity and the functionality Angular Forms provide.
Even though, naturally forms often require a lot of boilerplate. In this piece, we will leverage Angular forms to create a generic forms solution.
Custom reusable forms controls
Understanding The Problem
In one of my projects, I required to create an authentication module for a company in the e-commerce industry. It might sound simple but then I realize there are 8 different pages in such module — Login, Register, reset password, social login, merge account and more. Most of the pages were sharing the same forms elements, with the same UI, same validations and also the same error messages.
Let’s take the email input as a use case:
Email input initial in an empty state, When the input is valid we want to indicate it to the user with a V sign, and we want to show an error message when it’s not.
I’ve counted, and, in a simple authentication project like this one, I’ve used the same email input 12 times!
The way to the solution
We want to create a Reusable generic form control.
Usually, when we think about reusing something with Angular, the first thing comes to our minds is wrap it in a component.
The requirements are:
- Should fit for type text and password
- Should validate on required and pattern
- Should show indication whether the control is valid or not
- Should show error messages
Let’s start by creating the component and use the @Input() decorator to pass the configuration we need for our custom input:
And the template:
As you can see, the template has 4 sections. The input itself, which going to have a ngModel directive on it,Vsection if the input is valid, * section if the control is required and error message if the input is not valid.
Now, let’s try our custom input:
And the result (Check our `First Try` page — on the example section):
We get an exception right away.
What’s wrong? we’re trying to attach a form directive to something Angular doesn’t recognize as a form control.
The solution — The ControlValueAccessor interface:
We actually need a bridge between the Angular forms API and our native custom element. Angular provides us with an interface which let the framework know you can attach any form directive to it. The ControlValueAccessor interface.
The ControlValueAccessor interface has 4 methods, 3 of them are mandatory:
Let’s implement our first custom input control using the ControlValueAccessor interface:
And the generic-input.component template:
Now, implementing the interface is not enough, we also have to provide the NG_VALUE_ACCESSOR token in the component metadata:
We’ve created a custom form control, now let’s handle the validators as well. I’m going to implement the Validator interface:
And our GenericComponent will look like this:
Of course, don’t forget to add the provider for the NG_VALIDATORS token:
Let’s try it again:
We not really showing an indication of whether the control is valid or not.
Getting the control reference
We want to show the user an indication of whether the control is valid or not, but, we don’t have the control instance.
Maybe we can use the DI to inject it? Yes, we can!
Few important things here:
- We’re injecting the NgControl which is the super class of both formControlName and ngModel, with that, we’re not coupling our form control to any of the template or reactive module.
- We’re decorating our control with the @Self() decorator. That way we’re ensuring our control will not be overwritten by the injector tree.
- We’re setting the control valueAccessor to point to the GenericComponent class.
Now let’s update our template and use the control reference:
One thing we have to keep in mind is that NgControl already provides the NG_VALUE_ACCESSOR and the NG_VALIDATOR tokens. If we provide them in the GenericComponent class we might run into circular dependency issues, so — we better remove them:
We also need to set the validators for the control:
And the result (Checkout Login &Register pages in the examples sections):
Now everything Works!
Custom reusable forms
Understanding The Problem
Imagine a crazy scenario in which you want to use the same exact form in several places. Probably most of you needed to fill the address form twice in e-commerce websites if your billing address is different than your shipping address, right?
We don’t want to implement the same form functionality over and over again. We want to implement only one address form and reuse it.
Reusing? right, component!
The ControlValueAccessor, again:
Let’s first create our AddressComponent:
And the template:
Now, let’s leverage the ControlValueAccessor interface again, but now, to wrap the whole form and not a specific control:
Of course, don’t forget to provide the NG_VALUE_ACCESSOR token:
Let’s try it (Checkout Reusable forms — ControlValueAccessor page in the examples sections):
Maybe there is a shorter way?
Tired of implementing the ControlValueAccessor interface? me too.
In that case of reusing a complete form, we can actually use the Angular DI to inject the parent ControlContainer:
A thing to notice here is that we using the viewProviders and not the providers. The reason is Angular using the @Host() decorator on the ControlContainer injection on both FormControlName and NgModel implementation. (Checkout Angular source code: FormControlName Directive, NgModel Directive). For more information about the @Host() decorator I really recommend on this blog by Max Koretskyi aka Wizard.
After providing the ControlContainer, we can use the DI to inject it into our AddressComponent, and to set our address form to equal theControlContainer form ref:
And the Result (Checkout Reusable forms — SubForms page in the examples sections):
Simple, shorter, but restrictive:
We have to remember that once we provided the FormGroupDirective or the ngModelGroupe we’re creating a coupling to only one of the forms implementations (Template or Reactive).
Let’s summarize what we just learned:
- The ControlValueAccessor is a bridge between our components and the Angular Forms API, and will allow us to create reusable custom controls and custom reusable forms.
- The Angular DI can help us inject the NgControl and set his valueAccessor so we can have easy access to our control in the template.
- We can leverage the DI again to inject the FormGroupDirective or the ngModelGroupe to create a sub-form.
Thanks for reading!
Reducing the forms boilerplate — make your Angular forms reusable was originally published in Angular In Depth on Medium, where people are continuing the conversation by highlighting and responding to this story.