#7 Let's Add Some Validation
Friday, November 17, 2017
In this article we will update our component's template so that we can change the icon for our validators as the user types in the inputs causing their state to change. Although angular comes with several built in validator functions they do not contain all of the functionality that we require, and in terms of the min length validator do not behave as we would want it to, so we will create our first custom validator and apply it to the username input.
Parts
- Part 30: User Database
- Part 29: Canonical Url
- Part 28: Razor Page Handlers
- Part 27: Send Screen Finished
- Part 26: Stroke Dashoffset
- Part 25: Send Screen
- Part 24: Forgot Link Behaviour
- Part 23: Animating Controls (cont.)
- Part 22: Animating Controls
- Part 21: Hiding Control Groups
- Part 20: ModelState Errors (cont.)
- Part 19: ModelState Errors
- Part 18: Animating Info Pages (cont.)
- Part 17: Animating Info Pages
- Part 16: Keyboard Navigation
- Part 15: Accessing the DOM
- Part 14: All About the Username
- Part 13: CSRF Attacks
- Part 12: Http Requests
- Part 11: HMR
- Part 10: Color Inputs And Buttons
- Part 9: Animating Sub-Actions
- Part 8: Form Validation (cont.)
- Part 7: Form Validation
- Part 6: Form Group
- Part 5: Authenticator Validators
- Part 4: Authenticator Inputs
- Part 3: First Angular Component
- Part 2: Webpack
- Part 1: Beginning
You can view a completed example of the authenticator component here.
Private class field can be made readonly
-
WebUi
-
Source
-
components
- authenticator.component.ts
-
components
-
Source
Before we begin adding our validators I want to make a small 'correction' to the authenticator.component.ts file. I use the quotes around correction because this is not strictly speaking an error but it is still a change I would like to make. In the constructor of our component angular is injecting our uri parser service and we are not going to intentionally try to reassign it at any other point which means we can mark that service as readonly.
authenticator.component.ts (readonly)
constructor(private readonly _uriParser: UriParserService, fb: FormBuilder) {
...
}
Someone asked for some validation
-
WebUi
-
Source
-
components
- authenticator.component.pug
- authenticator.component.ts
-
components
-
Source
We will begin adding form validation to our component by modifying the validator mixin in our template file
(b). Our mixin will now take in the key, which specifies which
control we are talking about, and an error string so that we know what error we are talking about. This information will allow us to
use angular, through the [ngClass]
property binding, to apply a css class with help from our component's typescript file.
Before we get to that though
we need to adjust the calls that we are making to the validator mixin (c).
Here we have simply added the appropriate control key variable and a string, which again represents the type of error, to
each call of the validator mixin. In order to test this out we need to add the
getValidatorIconClass
method that we are calling in
(b) to our component's typescript file
(d).
authenticator.component.pug (mixin validator)
mixin validator(message, key, error)
div.info-validator
p.validator #{message}
i.validator-icon.fa([ngClass]=`getValidatorIconClass(${key}, '${error}')`)
authenticator.component.pug (invoke validator)
+info("Username")
+validator("Unique", controlUsernameKey, "unique")
+validator("Required", controlUsernameKey, "required")
+validator("Minimum length is 8", controlUsernameKey, "minlength")
+validator("Maximum length is 100", controlUsernameKey, "maxlength")
+validator("May only contain alphanumeric and '-._@+' characters", controlUsernameKey, "regexinvalid")
+info("Password")
+validator("Required", controlPasswordKey, "required")
+validator("Minimum length is 12", controlPasswordKey, "minlength")
+validator("Maximum length is 100", controlPasswordKey, "maxlength")
+validator("Must contain at least one lowercase letter", controlPasswordKey, "onelower")
+validator("Must contain at least one capital letter", controlPasswordKey, "onecapital")
+validator("Must contain at least one digit", controlPasswordKey, "onedigit")
+validator("Must contain at least one character that is not a letter or number", controlPasswordKey, "onenonalpahanumeric")
+info("Confirm Password")
+validator("The password and confirm password must match", controlPasswordConfirmKey, "nomatch")
+info("Email Address")
+validator("Required", controlEmailKey, "required")
+validator("Must be a valid email address", controlEmailKey, "emailregex")
+info("Confirm Email Address")
+validator("The email address and confirm email address must match", controlEmailConfirmKey, "nomatch")
authenticator.component.ts (1)
import { FormBuilder, FormControl, ValidatorFn } from "@angular/forms";
public getValidatorIconClass = (control: FormControl, error: string): string => {
console.log(`${control} ${error}`);
return "fa-times";
}
Once these changes are made and we refresh our browser we should see output in the browser's console that looks like (e).
By using the a string for the key in our template that is equal to name of our control properties angular is providing the control as the first argument in the
getValidatorIconClass
, which we defined in (d).
Our template is now wired up to handle updating our validator icons in real time so now we need to add the logic to determine if the value of an input is valid or not.
How about some validator functions
-
WebUi
-
Source
-
components
- authenticator.component.ts
-
forms
- form.validators.ts
-
components
-
Source
Since validation is something that we will need in just about every form we create we are going to add a new file to our project to hold
our validator functions named 'form.validators.ts' in the forms folder. Some of you may be saying that angular comes with a set of built
in validator functions, and that is true and we will be using them, but we will need additional functionally that the built in functions
do not provide. In addition to this by writing our own we will gain
a better understanding of the validation process. We will start by adding just one function, namely a min length validator, so that we can test that
everything is working correctly. Our validator functions will be static functions, following the design of the angular validator functions,
that will take in a configuration object and return a function that takes a form control as an argument and returns and object. The object
that is return can be null, which in general will be the way to say that there is not a validation error, or if there is an error the object
will contain a property with its name equal to the name of the error. For example in (f) we are defining
a min length validator that either returns the result of using the angular provided min length validator using
Validators.minLength(config.length)(c)
or if the length of the controls value is equal to zero our
custom object {"minlength": true}
. We are doing it this way because I want a control with a min
length set to be invalid from the start if its value is zero which is not the default angular behaviour.
The most import component of our validator function is the call to the XcValidators.skipValidation(c, config)
function. By defining a skip?: () => boolean;
function on the
config: ILengthValidatorConfig
object we can conditionally determine if the validator
should be applied or not. For now we will have to wait for a future article to know why this will be important. Now that we have a validator
defined we can apply it to our form controls. We will start by just adding it to our username control (g).
form.validators.ts (1)
import { FormControl, ValidatorFn, Validators} from "@angular/forms";
interface IValidatorConfig {
skip?: () => boolean;
}
export interface ILengthValidatorConfig extends IValidatorConfig {
length: number;
}
export class XcValidators {
public static minLength(config: ILengthValidatorConfig): ValidatorFn {
return (c: FormControl) => {
if (XcValidators.skipValidation(c, config)) {
return null;
}
return c.value.length === 0 ? { "minlength": true } : Validators.minLength(config.length)(c);
};
}
private static skipValidation = (c: FormControl, config: IValidatorConfig) => {
if(typeof config === "undefined" || config === null) {
return false;
}
if(typeof config.skip === "undefined" || config.skip === null) {
return false;
}
return config.skip();
}
}
authenticator.component.ts (2)
import { XcValidators } from "../../forms/form.validators";
public ngOnInit(): void {
...
controls[this.controlUsernameKey] = ["", XcValidators.minLength({
length: 8
})];
...
}
public getValidatorIconClass = (control: FormControl, error: string) => {
const hasError = control.hasError(error);
return hasError ? "fa-times" : "fa-check";
}
Once we have saved our changes we can see that if we type into the username input while the value is seven or less (h) the min length validator for the username displays the icon but once the value is length of eight the icon switches to (i).
In the next article we will create and use additional validators for our authenticator component.