Tuesday, February 11, 2020

Masked Input Length Validation in Angular

We use angular2-text-mask to mask some of our inputs at work (so phone number fields show parentheses and a hyphen, for example). I discovered today that using the built-in Angular minLength validator doesn't work with the text mask because the masked value meets the length criteria. So I wrote my own validator based on the built-in one that accepts an optional array of characters to replace and then only returns true if the modified length (that is, the length of the string after removing masking characters) is long enough (or short enough, depending on which validator you use). You know what? Let me just show you.

   1:  import { Injectable } from '@angular/core';
   2:  import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
   3:  
   4:  @Injectable({
   5:    providedIn: 'root'
   6:  })
   7:  export class MaskedLengthValidator {
   8:    static minLength(minLength: number, replace?: Array<string>): ValidatorFn {
   9:      return (control: AbstractControl): ValidationErrors | null => {
  10:        if (this.isEmptyInputValue(control.value)) {
  11:          return null;
  12:        }
  13:  
  14:        let value = control.value;
  15:  
  16:        if (!!replace && !!replace.length) {
  17:          replace.forEach(c => {
  18:            var regEx = new RegExp(c, 'g');
  19:            value = value.replace(regEx, "");
  20:          });
  21:        } else {
  22:          value = control.value.replace(/_/g, "");
  23:        }
  24:  
  25:        const length: number = value ? value.length : 0;
  26:        return length < minLength ?
  27:          {'minlength': {'requiredLength': minLength, 'actualLength': length}} :
  28:          null;
  29:      };
  30:    }
  31:  
  32:    static maxLength(maxLength: number, replace?: Array<string>): ValidatorFn {
  33:      return (control: AbstractControl): ValidationErrors | null => {
  34:        let value = control.value;
  35:  
  36:        if (!!replace && !!replace.length) {
  37:          replace.forEach(c => {
  38:            var regEx = new RegExp(c, 'g');
  39:            value = value.replace(regEx, "");
  40:          });
  41:        } else {
  42:          value = !!value ? control.value.replace(/_/g, "") : value;
  43:        }
  44:  
  45:        const length: number = value ? value.length : 0;
  46:        return length > maxLength ?
  47:          {'maxLength': {'requiredLength': maxLength, 'actualLength': length}} :
  48:          null;
  49:      };
  50:    }
  51:  
  52:    static isEmptyInputValue(value: any): boolean {
  53:      // we don't check for string here so it also works with arrays
  54:      return value == null || value.length === 0;
  55:    }
  56:  }
  57:  

So that's the validator. The simple way to use it is like this:
   1:    ngOnInit() {
   2:      this.someForm = this.formBuilder.group({
   3:        phoneNumber: [null, [Validators.required, MaskedLengthValidator.minLength(10), MaskedLengthValidator.maxLength(10)]]
   4:      });
   5:    }

And if you have placeholders in your mask other than underscores you can use it like this:
   1:    ngOnInit() {
   2:      this.someForm = this.formBuilder.group({
   3:        phoneNumber: [null, [Validators.required, MaskedLengthValidator.minLength(10, ['-']), MaskedLengthValidator.maxLength(10, ['-'])]]
   4:      });
   5:    }

This is pretty straightforward once you think about it. Now you don't have to think about.

No comments:

Post a Comment