javascriptangularangular-reactive-formscustom-validators

How to implement custom validator in generic validator class angular2?


I am trying implement custom validator into generic validator class, Basically I am aware of that to write normal custom validator in suppurate class but here facing a bit confusion to write in generic validator class. If any one knows please help me out.

Here is my generic-validator.ts file

import { FormGroup } from '@angular/forms';

// Generic validator for Reactive forms
// Implemented as a class, not a service, so it can retain state for multiple forms.
export class GenericValidator {

    // Provide the set of valid validation messages
    // Stucture:
    // controlName1: {
    //     validationRuleName1: 'Validation Message.',
    //     validationRuleName2: 'Validation Message.'
    // },
    // controlName2: {
    //     validationRuleName1: 'Validation Message.',
    //     validationRuleName2: 'Validation Message.'
    // }
    constructor(private validationMessages: { [key: string]: { [key: string]: string } }) {

    }

    // Processes each control within a FormGroup
    // And returns a set of validation messages to display
    // Structure
    // controlName1: 'Validation Message.',
    // controlName2: 'Validation Message.'
    processMessages(container: FormGroup): { [key: string]: string } {
        let messages = {};
        for (let controlKey in container.controls) {
            if (container.controls.hasOwnProperty(controlKey)) {
                let c = container.controls[controlKey];
                // If it is a FormGroup, process its child controls.
                if (c instanceof FormGroup) {
                    let childMessages = this.processMessages(c);
                    Object.assign(messages, childMessages);
                } else {
                    // Only validate if there are validation messages for the control
                    if (this.validationMessages[controlKey]) {
                        messages[controlKey] = '';
                        if ((c.dirty || c.touched) &&
                            c.errors) {
                            for (let messageKey in c.errors) {
                                if (c.errors.hasOwnProperty(messageKey) &&
                                    this.validationMessages[controlKey][messageKey]) {
                                    messages[controlKey] += this.validationMessages[controlKey][messageKey];
                                }
                            }
                        }
                    }
                }
            }
        }
        return messages;
    }
}

Here these are my parameters for custom validator i.e say : '22,3333,4,555,66' , [2,5] first one is comma separated string ... which might have entries of 2 or 5 long Here the condition is each comma suppurated string must be >2.


Solution

  • When I write custom validators it is typically to use with reactive forms. My custom validators are in a class that extends Validators from @angular/forms module. With this, you return null if the validation is good, and an object if it's bad.The following checks for invalid characters.

    import { FormControl, Validators, ValidatorFn } from '@angular/forms';
    
    // setup simple regex for white listed characters
    const validCharacters = /[^\s\w,.:&\/()+%'`@-]/;
    
    // create your class that extends the angular validator class
    export class CustomValidators extends Validators {
    
     // create a static method for your validation
     static invalidateCharacters(control: FormControl) {
    
        // first check if the control has a value
        if (control.value && control.value.length > 0) {
    
          // match the control value against the regular expression
          const matches = control.value.match(invalidCharacters);
    
          // if there are matches return an object, else return null.
          return matches && matches.length ? { invalid_characters: matches } : null;
        } else {
          return null;
        }
      }
    }
    

    Make a FormErrorService that builds up your error message:

    import { Injectable } from '@angular/core';
    import { FormGroup } from '@angular/forms';
    
    @Injectable()
    export class FormErrorService {
    
      // return list of error messages
      public validationMessages() {
        const messages = {
          required: 'This field is required',
          email: 'This email address is invalid',
          is1980OrLater: 'Please enter a date that is after 01/01/1980.',
          maxDateFailed: (failText: string) => {
            return failText;
          },
          minDateFailed: (failText: string) => {
            return failText;
          },
          invalid_characters: (matches: any[]) => {
    
            let matchedCharacters = matches;
    
            matchedCharacters = matchedCharacters.reduce((characterString, character, index) => {
              let string = characterString;
              string += character;
    
              if (matchedCharacters.length !== index + 1) {
                string += ', ';
              }
    
              return string;
            }, '');
    
            return `These characters are not allowed: ${matchedCharacters}`;
          },
        };
    
        return messages;
      }
    
      // Validate form instance
      // check_dirty true will only emit errors if the field is touched
      // check_dirty false will check all fields independent of
      // being touched or not. Use this as the last check before submitting
      public validateForm(formToValidate: FormGroup, formErrors: any, checkDirty?: boolean) {
        const form = formToValidate;
    
        for (const field in formErrors) {
          if (field) {
            formErrors[field] = '';
            const control = form.get(field);
    
            const messages = this.validationMessages();
            if (control && !control.valid) {
              if (!checkDirty || (control.dirty || control.touched)) {
                for (const key in control.errors) {
                  if (key && key !== 'invalid_characters') {
                    formErrors[field] = formErrors[field] || messages[key];
                  } else {
                    formErrors[field] = formErrors[field] || messages[key](control.errors[key]);
                  }
                }
              }
            }
          }
        }
        return formErrors;
      }
    }
    

    Where you're building the form in your comopnent:

        import {CustomValidators} from 'filepath';
        import {FormErrorService} from 'formerrorservicepath';
        myFormGroup: FormGroup;
        public formErrors = {
        myInput: ''
      };
      formErrors = [];
      constructor(
        public formErrorService: FormErrorService
      ) {}
        // then in your ngOnInit 
        this.myFormGroup = new FormGroup({});
        this.myFormGroup.addControl('myInput', new FormControl());
        this.myFormGroup.get('myInput').setValidators(Validators.compose([CustomValidators.invalidCharacters]);
    
    this.myFormGroup.valueChanges.subscribe(data => {
          this.formErrors = [];
            this.formErrors = this.formErrorService.validateForm(
              this.myFormGroup,
              this.formErrors,
              true
            );
          })
    

    Now in your HTML:

    <form [formGroup]="myFormGroup">
    <div>
    <input type="text" formControlName="myInput"/>
    <p *ngFor="let error of formErrors">
    {{error}}
    </p>
    <button type="button" [diabled]="!myFormGroup.valid">Action Button</button>
    </div>
    </form>