Angular 两个字段之间的条件要求验证器

Angular conditional required validator between two fields

我需要对表单进行验证以使用户至少填写两个字段之一

我试过这样的自定义验证器:

export function conditionalValidator(
  predicate: () => boolean,
  validator: ValidatorFn
): ValidatorFn {
  return formControl => {
    if (!formControl.parent) {
      return null;
    }
    let error = null;
    if (predicate()) {
      error = validator(formControl);
    }        
    return error;
  };
}

然后在我的表单中

this.myForm= this.fb.group({
    field1: ['', conditionalValidator(() => !(this.myForm.get('field1').value || this.myForm.get('field2').value), Validators.required)],
    field2: ['', conditionalValidator(() => !(this.myForm.get('field1').value || this.myForm.get('field2').value), Validators.required)],
});

this.myForm.get('field1').valueChanges.subscribe(_ => {
    this.myForm.updateValueAndValidity();
});
this.myForm.get('field2').valueChanges.subscribe(_ => {
    this.myForm.updateValueAndValidity();
});

但是不知道为什么不行

我也尝试过更“手工”的方式 (stackblitz link here),但也没有成功

这就是组验证器(而非字段验证器)的用途。

this.myForm= this.fb.group({
    field1: ['', Validators.required],
    field2: ['', Validators.required],
}, { 
    validators: [/* put your multi-field validator here */],
}});

正如 mbojko 所说,您可以在整个 formGroup 上创建自定义验证器。这使得验证器在表单中的每个更改都被执行。如果只有这两个字段就是最优解。

但是您也可以在一个字段上使用自定义验证器,问题是只有在更改表单控件时才执行验证器,但首先我们将看到验证器

export function conditionalValidator(field:string): ValidatorFn {
  return (formControl) => {
    if (!formControl.parent) {
      return null;
    }
    return formControl.value || formControl.parent.get(field).value?
           null: {error:"this field or "+field+" is required"}
  };
}

然后你使用 like

this.myForm = this.fb.group({
  myField: ["",conditionalValidator("otherField")],
  otherField: ["",conditionalValidator("myField")],
});

好吧,正如我所说,当我们将自定义表单控件制作成一个中继到另一个控件的控件时,问题是我们需要检查另一个控件何时发生更改。您订阅控件的 valueChanges。您可以使用 merge rxjs 运算符来获得唯一的订阅

merge(this.myForm.get("otherField").valueChanges,
      this.myForm.get("myField").valueChanges)
      .subscribe(_ => {
      this.myForm.get("otherField").updateValueAndValidity({emitEvent:false});
      this.myForm.get("myField").updateValueAndValidity({emitEvent:false})
    });

你的forked stackblitz

Update 好吧,我们知道每次我们的控件更改时都会执行验证器,所以为什么不考虑这一点并在此函数中对其他字段进行 updateValueAndValidity ?

好吧,只有当 is 有效且有错误或无效且没有错误时我们才需要执行,否则我们会得到一个递归错误

export function conditionalValidator(field: string): ValidatorFn {
  return formControl => {
    if (!formControl.parent) {
      return null;
    }
    const otherControl = formControl.parent.get(field);
    const error =
      formControl.value || otherControl.value
        ? null
        : { error: "this field or " + field + " is required" };
    if ((error && otherControl.valid) || (!error && otherControl.invalid)) {
      setTimeout(() => {
        otherControl.updateValueAndValidity({ emitEvent: false });
      });
    }
    return error;
  };
}

所以,我们不需要订阅

看到一个new stackblitz