Advertisement

#7 Let's Add Some Validation

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.

You can view a completed example of the authenticator component here.

Private class field can be made readonly

  • WebUi
    • Source
      • components
        • authenticator.component.ts

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) {
    ...
}
(a) Marking the uri parser service as readonly so that we receive a compiler error if we accidentally try to reassign it in the future.

Someone asked for some validation

  • WebUi
    • Source
      • components
        • authenticator.component.pug
        • authenticator.component.ts

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}')`)
(b)

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")
(c)

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";
}
(d)

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).

Temporary console output of the getValidatorIconClass method.
(e) Temporary console output of the getValidatorIconClass method showing that the first parameter is an object, in this case a form control, and that the second is the name of the error that is also passed in.

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.

Advertisement

How about some validator functions

  • WebUi
    • Source
      • components
        • authenticator.component.ts
      • forms
        • form.validators.ts

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();
    }
}
(f) The min length validator...our first validator.

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";
}
(g) Implementing the min length validator for our username control.

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).

Image showing that the min length validator is invalid for a value of length 7.
(h) Image showing that the min length validator is invalid for a value of length 7.
Image showing that the min length validator is valid for a value of length 8.
(i) Image showing that the min length validator is valid for a value of length 8.

In the next article we will create and use additional validators for our authenticator component.

Exciton Interactive LLC
Advertisement