1

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(['[email protected]'], [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:

[ '[email protected]', '[email protected]' ]

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

2 Answers 2

1
+50

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the link on the Angular issue, it's a bit crazy that this issue was opened for so long. Your workaround works perfectly, it does the job for me. Thanks
1

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

Every validator function returns an error object. If you see the Validators.email() from angular, it returns { 'email': true } object. So if you loop through control.errors (or hasError in your example), you can check if the key for any object matches email -

static email(control) {
        if (isEmptyInputValue(control.value)) {
            return null; // don't validate empty values to allow optional controls
        }
        return EMAIL_REGEXP.test(control.value) ? null : { 'email': true };
    }

Below is how your validate() function will look like -

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]) => {
          if(errorName === 'email' ) {
            console.log('Validators.email was set');
          }
          allErrors[errorName] = isOnError;
        });
      }
    }

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

    return null;
  }

5 Comments

The problem is with this line: "const hasError = validator(c);" Because of my custom implementation of my formControl, this doesn't work. It doesn't work because the formControl is an array of string. That's why I want to know what is the validator before this line to be able to apply custom logic to check if the formControl is valid or not
Do you have a custom email validator? Can you please show it? If built-in angular validators are working with your custom component when the email control is not an array, your custom email validator should work too (if implemented correctly). Can you reproduce your problem on StackBlitz.com? It makes it really easy for others to clearly understand what you're trying to achieve.
I know, it's just a lot more work for me to reproduce on a stackblitz. I will do that next week. I don't yet have the custom email validators. the problem is not with the validator itself, the problem is: "How to know that the parent component uses Validators.email" the build in validator doesn't work with my data structure (because it's custom - it make sense), so what I want is: If the parent use Validators.email => implement my own logic. I just don't know how to find out which validator was set from the Parent
Did you try using if(validator === Validators.email){} before const hasError = validator(c)?
I did and it's not working, validator is a function that takes a formControl as argument. That was a nice try though. :-)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.