#8 More Form Validation
Friday, November 24, 2017
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.
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.
We need more than a min length validator
-
WebUi
-
Source
-
components
- authenticator.component.ts
-
forms
- form.validators.ts
-
components
-
Source
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();
}
}
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
}));
}
}
In the next article we will continue to modify our template so that we can add additional behaviours and functionality.