Advertisement

#8 More Form Validation

In the previous article we started laying the ground work for creating the custom form validators that we need for our component. In that article we did in fact create our first validator for min length. In this article we will create the rest of the validators that we need as well as create a function that will allow us to compose them all together for inputs that require more than one validator.

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

We need more than a min length validator

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

Unfortunately for us we need more than just a min length validator that we created in the previous article. To handle all of the validation that we need for our authenticator component we will add more validator functions to our form.validators.ts file. We will replace the code in the form.validators.ts file, that we have already created, with that found in (a). Once that is complete we will modify our authenticator.component.ts file by creating a new method called createFormGroup that we will use to create our form group and apply all the necessary validation, and modify our ngOnInit method to call it.

form.validators.ts (2)

import loDashForOwn = require("lodash/forOwn");
import { FormControl, ValidatorFn, Validators } from "@angular/forms";

interface IValidatorConfig {
    skip?: () => boolean;
}

interface IErrorNameValidatorConfig {
    error?: string;
}

export interface IEmailRegexValidatorConfig extends IValidatorConfig, IErrorNameValidatorConfig { }

export interface IEmailRegexValidatorAndConfig {
    validator: (config?: IEmailRegexValidatorConfig) => ValidatorFn;
    config?: IEmailRegexValidatorConfig;
}

export interface ILengthValidatorConfig extends IValidatorConfig {
    length: number;
}

export interface ILengthValidatorAndConfig {
    validator: (config: ILengthValidatorConfig) => ValidatorFn;
    config: ILengthValidatorConfig;
}

export interface IMatchValidatorConfig extends IValidatorConfig, IErrorNameValidatorConfig {
    other: FormControl
}

export interface IMatchValidatorAndConfig {
    validator: (config: IMatchValidatorConfig) => ValidatorFn;
    config: IMatchValidatorConfig;
}

export interface IRegexValidatorConfig extends IValidatorConfig, IErrorNameValidatorConfig {
    regex: RegExp,
}

export interface IRegexValidatorAndConfig {
    validator: (config: IRegexValidatorConfig) => ValidatorFn;
    config: IRegexValidatorConfig;
}

export interface IRequiredValidatorAndConfig {
    validator: (config?: IValidatorConfig) => ValidatorFn;
    config: IValidatorConfig;
}

export interface IUniqueValidatorConfig extends IValidatorConfig, IErrorNameValidatorConfig {
    pass: (value: string) => boolean;
}

export interface IUniqueValidatorAndConfig {
    validator: (config: IUniqueValidatorConfig) => ValidatorFn;
    config: IUniqueValidatorConfig;
}

export interface IUserDefinedValidatorConfig extends IValidatorConfig {
    error: string;
    pass: (value: string) => boolean;
}

export interface IUserDefinedValidatorAndConfig {
    validator: (config: IUserDefinedValidatorConfig) => ValidatorFn;
    config: IUserDefinedValidatorConfig;
}

export class XcValidators {
    public static emailRegularExpression = /^[a-z0-9!#$%&"*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;

    public static compose(validators: { [key: string]: { validator: (config?: any) => ValidatorFn, config?: any } }, skip?: () => boolean): ValidatorFn  {
        const vCollection: ValidatorFn[] = [];
        loDashForOwn(validators, o => {
            const config = o.config || {};
            config.skip = skip;
            vCollection.push(o.validator(config));
        });
        return Validators.compose(vCollection);
    }

    public static emailRegex(config?: IEmailRegexValidatorConfig): ValidatorFn {
        return (c: FormControl) => {
            config = config || {};
            return XcValidators.regex({
                error: config.error || "emailregex",
                regex: XcValidators.emailRegularExpression,
                skip: config.skip
            })(c);
        }
    }

    public static match(config: IMatchValidatorConfig): ValidatorFn {
        return (c: FormControl) => {
            if (XcValidators.skipValidation(c, config)) {
                return null;
            }
            if (typeof config.other === "undefined" || config.other === null) {
                return null;
            }

            var error = config.error || "nomatch";
            return XcValidators.getResponse(error, c.value === config.other.value);
        };
    }

    public static maxLength(config: ILengthValidatorConfig): ValidatorFn {
        return (c: FormControl) => {
            if(XcValidators.skipValidation(c, config)) {
                return null;
            }
            return Validators.maxLength(config.length)(c);
        }
    }

    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);
        }
    }

    public static noop() {
        return (): {} => {
            return null;
        };
    }

    public static regex(config: IRegexValidatorConfig): ValidatorFn {
        return (c: FormControl) => {
            if (XcValidators.skipValidation(c, config)) {
                return null;
            }

            var error = config.error || "regexinvalid";
            return XcValidators.getResponse(error, config.regex.test(c.value));
        };
    }

    public static required(config?: IValidatorConfig): ValidatorFn {
        return (c: FormControl) => {
            config = config || {};
            if (XcValidators.skipValidation(c, config)) {
                return null;
            }
            return Validators.required(c);
        }
    }

    public static unique(config: IUniqueValidatorConfig): ValidatorFn {
        const c: IUserDefinedValidatorConfig = {
            error: config.error || "unique",
            pass: config.pass
        };
        return XcValidators.userDefined(c);
    }

    public static userDefined(config: IUserDefinedValidatorConfig): ValidatorFn {
        return (c: FormControl) => {
            if (XcValidators.skipValidation(c, config)) {
                return null;
            }

            return XcValidators.getResponse(config.error, config.pass(c.value));
        };
    }

    private static getResponse(name: string, pass: boolean) {
        if(pass) {
            return null;
        }
        const errorObj = {};
        errorObj[name] = { valid: false };
        return errorObj;
    }

    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();
    }
}
(a) All of the validator functions necessary for our authenticator component.

authenticator.component.ts

export class AuthenticatorComponent implements OnInit {
    ...
    import { XcValidators, IEmailRegexValidatorAndConfig, IMatchValidatorAndConfig, ILengthValidatorAndConfig, 
         IRegexValidatorAndConfig, IRequiredValidatorAndConfig, IUniqueValidatorAndConfig } from "../../forms/form.validators";
    ...
    public ngOnInit(): void {
        this.createFormGroup();
    }
    ...
    private createFormGroup = () => {
        const controls: { [key: string]: [string|boolean, ValidatorFn] } = {};
        controls[this.controlEmailKey] = ["", 
            XcValidators.compose({
                "required": {
                    validator: XcValidators.required
                } as IRequiredValidatorAndConfig,
                "regex": {
                    validator: XcValidators.emailRegex
                } as IEmailRegexValidatorAndConfig
            })
        ];
        controls[this.controlEmailConfirmKey] = [null, XcValidators.noop()];
        controls[this.controlPasswordKey] = ["", 
            XcValidators.compose({
                "required": {
                    validator: XcValidators.required
                } as IRequiredValidatorAndConfig,
                "maxlength": {
                    validator: XcValidators.maxLength,
                    config: { length: 100 }
                } as ILengthValidatorAndConfig,
                "minlength": {
                    validator: XcValidators.minLength,
                    config: { length: 12 }
                } as ILengthValidatorAndConfig,
                "atLeastOneCapital": {
                    validator: XcValidators.regex,
                    config: { regex: /([A-Z])+/, error: "onecapital" }
                } as IRegexValidatorAndConfig,
                "atLeastOneDigit": {
                    validator: XcValidators.regex,
                    config: { regex: /([0-9])+/, error: "onedigit" }
                } as IRegexValidatorAndConfig,
                "atLeastOneLowercase": {
                    validator: XcValidators.regex,
                    config: { regex: /([a-z])+/, error: "onelower" }
                } as IRegexValidatorAndConfig,
                "atLeastOneNonAlphaNumeric": {
                    validator: XcValidators.regex,
                    config: { regex: /([^.\s0-9a-zA-Z])+/, error: "onenonalphanumeric" }
                } as IRegexValidatorAndConfig
            })
        ];
        controls[this.controlPasswordConfirmKey] = [null, XcValidators.noop()];
        controls[this.controlRememberMeKey] = [false, XcValidators.noop()];
        controls[this.controlUsernameKey] = ["", 
            XcValidators.compose({
                "required": { 
                    validator: XcValidators.required 
                } as IRequiredValidatorAndConfig,
                "maxlength": {
                    validator: XcValidators.maxLength,
                    config: { length: 100 }
                } as ILengthValidatorAndConfig,
                "minlength": {
                    validator: XcValidators.minLength,
                    config: { length: 8 }
                } as ILengthValidatorAndConfig,
                "regex": {
                    validator: XcValidators.regex,
                    config: { regex: /^[a-z0-9-._@+]+$/i }
                } as IRegexValidatorAndConfig,
                "unique": {
                    validator: XcValidators.unique,
                    config: {
                        pass: () => {
                            return true;
                        }
                    }
                } as IUniqueValidatorAndConfig
            })
        ];
        this._formController.create(controls);

        this.controlPasswordConfirm.setValidators(XcValidators.compose({
            "required": {
                validator: XcValidators.required
            } as IRequiredValidatorAndConfig,
            "match": {
                validator: XcValidators.match,
                config: {
                    other: this.controlPassword
                }
            } as IMatchValidatorAndConfig
        }));

        this.controlEmailConfirm.setValidators(XcValidators.compose({
            "required": {
                validator: XcValidators.required
            } as IRequiredValidatorAndConfig,
            "match": {
                validator: XcValidators.match,
                config: {
                    other: this.controlEmail
                }
            } as IMatchValidatorAndConfig
        }));
    }
}
(b) Modifying our component's typescript file to handle our current validation requirements.

In the next article we will continue to modify our template so that we can add additional behaviours and functionality.

Exciton Interactive LLC
Advertisement