angularcontrolvalueaccessorangular-custom-validators

How to use Built-in Angular validator within custom ControlValueAccessor


I have a custom component inheriting ControlValueAccessor on my application that integrate ng-select

The goal of this component is to have one central place where ng-select is integrated so I can re-use it across the application.

This is how I call the validation method:

  constructor(
    @Optional() @Self() public controlDir: NgControl
  ) {
    controlDir.valueAccessor = this;
  }

  ngOnInit() {
    const control = this.controlDir.control;
    if (control.validator) {
      control.setValidators([this.validate.bind(this, [control.validator])]);
    }
    control.updateValueAndValidity({ emitEvent: false });
  }

  validate(validators: ValidatorFn[], c: FormControl) {
    const allErrors: any = {};
    for (const validator of validators) {
      const hasError = validator(c);
      if (hasError) {
        Object.entries(hasError).forEach(([errorName, isOnError]) => {
          allErrors[errorName] = isOnError;
        });
      }
    }

    if (Object.keys(allErrors).length > 0) {
      return allErrors;
    }

    return null;
  }

And this is how I would typically instantiate the formControl on the parent component:

const control = this.formBuilder.control(['test@gmail.com'], [Validators.email]);

this.form = this.formBuilder.group({ test: control });

I want to give the parent the option to use Built-in angular validators on my custom component. (required, email, min, max...).

The problem is that the control value of my custom component is an array of string, for instance the value will be:

[ 'test@gmail.com', 'jeff@gmail.com' ]

the component looks like this

component screenshot

Problem: in my validate function, I have access to a ValidatorFn that will check if my control is a valid email. if my control value was a string, it would work as expected but it's an array of string, so it's not working.

So my guess is that i need to re-implement the email validator for my custom component (as it makes sense that angular can't figure out magically the structure of my data within my custom component).

But I can't figure out how to identify that the validator defined is Validator.email.

this.controlDir.control.validator is a function and I have no clue how to identify that it's the email validator so I can add my custom validation for emails.

Question: How can I know from my custom validate function which Validator was set from the parent? Was it Validators.required, Validators.email ...etc


Solution

  • How can I know from my custom validate function which Validator was set from the parent?

    Unfortunately there is no way to get the validators for a given control (details).

    In theory, you could iterate over your control value (if it is an array) and create a new FormControl to validate each string in the array.

    As an example you could do it like this:

    isControlValid(controlValue: string[], validator: ValidatorFn) {
      let emailHasError = false;
      for (value of controlValue) {
        const ctrl = new FormControl(value, validator);
        if (Object.keys(validator(ctrl)).length) {
          emailHasError = true; // if any of the values are invalid
        }
      }
      return emailHasError; // or return whatever you need to.
    }
    

    Use it like this perhaps

    validate(validators: ValidatorFn[], c: FormControl) {
      const allErrors: any = {};
      for (const validator of validators) {
        if (Array.isArray(c.value)) {
          if (this.isControlValid(c.value, validator)) {
            // maybe update `allErrors` here or something
    

    You can implement it however you want, but the idea is simply to validate each string using it's own form control.

    There might be some obscure way to achieve this differently using NG_VALIDATORS but I have not looked into it.