Angular Custom Validator with Multiple Errors

Angular Custom Validator

This is a quick addition to the knowledge base, answering: how do I use an Angular Custom Validator and return multiple validation errors and results results on the same control? For supporting documentation on how to add custom validators to reactive and template driven forms, see the official Angular documentation @ https://angular.io/guide/form-validation.

Applying the Custom Validator

To associate a custom validator with a FormControl in a reactive form, you might do the following in your component:

productForm: FormGroup;
productkey: AbstractControl;
constructor(fb: FormBuilder) {
    /*
     * apply a required and custom validator to the productkey control.
     */
    this.productForm = fb.group({
        'productkey':['', Validators.compose([Validators.required,
                                              productkeyValidator])],
    });
    this.productkey = this.productForm.controls['productkey'];
}

In the example above, I have a “Product Key” control in my form, and I want to enforce:

  • That it’s required; and
  • That it matches a set of specifications to be validated in my custom validator, productkeyValidator

Defining the Custom Validator

To define my custom validator, I’ll add a function outside of my Component class which returns a StringMap. The signature is as follows:

function productkeyValidator(control: FormControl): {[s:string]: boolean} {

}

The straightforward way to return multiple keys is to create your own StringMap object which is correctly typed per the function’s signature:

function productkeyValidator(control: FormControl): {[s:string]: boolean} {
  let validationMap: new vObj<boolean> = {};
  return validationMap;
}

What is “vObj”?

That is a custom interface I created because I’m not able to instantiate a StringMap object. When I do, I receive:

error TS2304: Cannot find name 'StringMap'

..so my work-around was to create my own interface that matches the documented Validator interface of

{[string: s]: boolean}

Returning Multiple Results

With the map in place, it’s straightforward to return multiple keys from the validator. The validator is going to check and see if the value begins with ‘002’ and ends with ‘XYZ’. If it doesn’t begin with ‘002’, print an error string, and the same if it doesn’t end with ‘XYZ’.

Disclaimer: This example is based on production code that I can’t reasonably refactor for a blog post. So, yes, with a validation as simple as this example, you would certainly just use a single check and print a single message explaining the constraints; e.g. “The Product Key must begin with ‘002’ and end with ‘003’”.

However, in the production case there are 4 significantly distinct validations, and checking-and-printing those validations separately was definitely the favorable user experience versus a single validation explanation to the tune of, “The product key must A, B, C, and D”.

So, we can just populate the map and return:

interface vObj {
    [s: string]: T;
}
function productkeyValidator(control: FormControl): {[s: string]: boolean}  {
  let validationMap: vObj = {};
  if (!control.value.match(/^123/)) {
    validationMap['123'] = true;
  }
  if (!control.value.match(/XYZ$/)) {
    validationMap['XYZ'] = true;
  }
  return validationMap;
}

 

And, to use them in the form:

<div *ngIf="productkey.hasError('123')">
    The Product Key must begin with "123".
</div>
<div *ngIf="productkey.hasError('XYZ')">
    The Product Key must end with "XYZ".
</div>

Angular Custom Validator in the UI.

 

Thanks for reading.



Categories: Angular, Apps

Tags:

Leave a Reply

%d bloggers like this: