Reactive FormGroup validation with AbstractControl in Angular 2 ?
Validation in Angular (v2+), various approaches, various APIs to use. We’re going to use AbstractControl to learn how to validate a particular FormGroup. I covered FormGroup, FormControl and FormBuilder in my previous reactives form fundamentals article - which I’d recommend checking out before this one if you’re new to Angular forms.
If the above makes no sense, go here then drop back! When our FormBuilder, i.e. the fb injected FormBuilder instantiates new groups through this.fb.group(), each of those is technically a new FormGroup(). So when we refer to “FormGroups”, this is what we’re talking about from here on out.
Before we can learn “how to do custom validation”, we must dive into
the workings of the APIs first to see what’s happening and actually have
some idea what’s going on, so let’s do that real quick. Here’s the
syntax for the FormBuilder class:
This means we can pass a controlsConfig Object down into the FormBuilder. This is what happens when we call this.fb.group(). We also have an optional extra? property, and finally : FormGroup, which is the return value. So essentially, FormBuilder is just an abstraction/wrapper at this point.
So, what do the internals look like?
The first line of code we completely know already, it’s just the syntax from above. Now, what is this extra argument that’s being passed in? Here’s where it’s used:
Interesting, it checks the presence of the extra “thing”, and providing it’s there and is in fact an Object, it’ll grab the validator property from it. Which means that the extra thing which is the optional second function argument, in fact looks like this when creating a group() with FormBuilder:
We can pass a second argument (or third, for asyncValidator) that gets passed to new FormGroup() instance. One more thing before we implement validation, we’ll see how FormGroup handles this internally:
FormGroup actually extends AbstractControl and then passes validator and asyncValidator to the AbstractControl through the super() call, which calls the constructor of the parent abstract class.
We won’t dive into the specifics of AbstractControl, but we know that it’s essentially the mothership of our form that sets, controls, and powers all things such as dirty, pristine, touched and other funky abstract methods we can touch when we ask the AbstractControl.
This next section will give you an insight on AbstractControl, however using AbstractControl is not essential in this case to implementing our custom FormGroup validation, as we can also inject FormGroup to talk to our form controls also - but this means the “control” that’s injected needs to be a FormGroup instance, so we can use AbstractControl instead for consistency.
Let’s circle back around and take a look at our original piece of code:
What we’re going to add is a custom validator that ensures when our lovely fake users sign up to our fake form, that their email and confirm email fields both match up. Using AbstractControl we can do this, but first, we need to actually compose the validation function:
We’ll add this inside email-matcher.ts for the sake of breaking code up into different files. This will allow us to then inject it into our emailMatcher validator into our FormGroup or FormBuilder wrapper.
Next step, we’ll inject AbstractControl:
So, we know now that AbstractControl is the mothership of our form that other form controls simply extend/inherit from, which means we can actually talk to any form control in the group. If you recall from the previous article, we can fetch information about our form controls via .get(<control>) to implement client-side validation errors, for example:
<divclass="error"*ngIf="user.get('foo').touched && user.get('foo').hasError('required')">
This field is required
</div>
Incidentally, we can also use this same API when implementing custom
validators, so referencing our previous form group code, in which we
have nested FormGroup props email and confirm, let’s go grab them:
Now we’re ready to do some fun stuff! All we actually want to do is compare that both the email and confirm fields have the same value, which will in turn display errors if they are invalid. Let’s check the .value property (the actual FormControl value, i.e. the <input>) and if they match we’ll return null (which internally sets the validation state for the entire group, and entire form where applicable):
What we want to implement is the validation that matches this HTML:
...
<divformGroupName="account"><label><span>Email address</span><inputtype="email"placeholder="Your email address"formControlName="email"></label><label><span>Confirm address</span><inputtype="email"placeholder="Confirm your email address"formControlName="confirm"></label><divclass="error"*ngIf="user.get('account').touched && user.get('account').hasError('nomatch')">
Email addresses must match
</div></div>
...
Ignoring the HTML, we’re interested specifically in this piece:
user.get('account').hasError('nomatch')
This means that we want to be able to query the account level FormGroup,
and check if it has an error called “nomatch”. To implement this we
require a custom Object to be returned from our validator should the
values not match:
No comments:
Post a Comment