1
votes

How to implement a custom validator which is applied only when the form control is valid?

Something like this would be ideal:

static isValid(control: FormControl) {
    if (control.valid) {
        // custom validation checks here
        return {isNotValid: true};
    }
    return null;
}

but here control.valid is always true, so it will be applied even if other will invalidate the field.

Is there a way to achieve this?

Detailed example

Source code here: https://stackblitz.com/edit/angular-conditional-validator

app.component.ts

import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MyValidator } from './validators.service';

@Component({
  selector: 'my-app',
  template: `
    <form>
      <label>Name:</label>
      <input [formControl]="form.get('name')">
    </form>
  `
})
export class AppComponent  {
  form = new FormGroup ({
    name: new FormControl('', [
      MyValidator.isValidString,
      MyValidator.isValidName,
    ])
  });
}

validators.service.ts

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

export class MyValidator {
  static isValidString(control: FormControl) {
    if (!control.value || typeof control.value !== 'string') {
      return {isNotValidString: true};
    }
    return null;
  }

  static isValidName(control: FormControl) {
    if (control.valid && control.value !== 'John Doe') {
      return {isNotValidName: true};
    }
    return null;
  }
}

How to make that isValidName validator is applied/executed only when control is valid, i.e. the previous validators returned null? Cause right now, I believe angular will first run all sync validators, then will run all async validators, and only after will set the control status, which is the correct approach I think.

Note
This example is for demonstration only, it has no real live application.

2
why control.valid is always valid ? edit your question to add a minimum verifiable example. - HDJEMAI
@HDJEMAI I've updated the question and added a link to a live example. Hope this will clarify my dilemma. - Ivan G
Could you explain the use case here, why do you need it only applied when control is otherwise valid? Why wouldn't the code now as such be okay? - AJT82
First, for the errors, I want to show only relevant error messages. Second, to reduce useless calculations, this code is just a mock, in a real application validators are obviously more complex and could have some impact on performance. - Ivan G

2 Answers

0
votes

The simplest way to achieve this was to create a helper function:
validators.service.ts

import { AbstractControl, FormControl, ValidationErrors, ValidatorFn } from "@angular/forms";

export function runInOrder(validators: ValidatorFn[]): ValidatorFn {
  return (c: AbstractControl): ValidationErrors | null => {
    for (const validator of validators) {
      const resp = validator(c);
      if (resp != null) {
        return resp;
      }
    }
    return null;
  };
}

export class MyValidator {
  // ...
}

and use it as a custom validator:
app.component.ts

import { MyValidator, runInOrder } from "./validators.service";

// ...
export class AppComponent {
  form: FormGroup = new FormGroup({
    name: new FormControl("", runInOrder([
      MyValidator.isValidString,
      MyValidator.isValidName
    ]))
  });
}

Full example here:
https://stackblitz.com/edit/angular-conditional-validator-solution

-1
votes

Try accessing parent property, assuming isValid validator is for the control which is first level child of your form control or else recursively find the parent to the top

static isValid(control: FormControl) {
    if(control.parent) {
       if(control.parent.valid) {
       //custom validation
       }
    }
 return null;
}