简体   繁体   中英

Angular 2 reactive forms custom validator apply only when form control is valid

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, ie 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.

try to access control status and if it's valid do your logic

if(control.status == 'VALID') {

}

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

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM