Angular 5: Reactive Forms - 表单控件更新时重新触发表单组验证

Angular 5: Reactive Forms - Form group validation retrigger when form control updated

我正在使用 Angular 5 Reactive forms,并设置了一个 form groupconfirmEmail 和两个 form controls 作为 emailconfirmEmail。我进一步向我的 form group 添加了一个 validator 并且 form controls 附加了 required 验证。

My use case is:我想根据各个表单控件的值触发custom validation,如果自定义验证器returns为invalid,则标记表单控件有错误。

My issue is that:一旦 form controls 被标记为错误,自定义验证器(在表单组上)不会触发,直到我再次触摸两个表单控件并更改两者的值他们中的。 理想情况下,我只想更新其中一个字段(以匹配另一个字段)并能够再次触发自定义验证。

示例代码:

@Component({...})
export class FormComponent implements OnInit {
  user: FormGroup;
  constructor(private fb: FormBuilder) {}
  ngOnInit() {
    this.user = this.fb.group({
      data: this.fb.group({
        email: ['', Validators.required],
        confirmEmail: ['', Validators.required]
      }, , { validator: validateEmail })
    });
  }

  const validateEmail = (control: AbstractControl): {[key: string]: boolean} => {
    const email = control.get('email');
    const confirmEmail = control.get('confirmEmail');

    if (email.value === confirmEmail.value) {
          return null;
     } else {
       this.setFieldAsInvalid(email);
       this.setFieldAsInvalid(confirmEmail);
       return { nomatch: true };
     }
  };

  private setFieldAsInvalid(field: AbstractControl) {
      field.setErrors({
        'invalid': true
      });
   }
}

解决这个问题的任何建议或我应该采取的任何其他方法。

当控件(email 和 confirmEmail)保持不同的值时,您正在将它们更改为无效,但是 ValidatorFun 中的 return null 只会将当前表单控件的有效更改为 true,不是所有的电子邮件和确认电子邮件


解决方案1: 您可以通过 field.setErrors(null) 将 email 和 confirmEmail 改回有效 当它们保持相同的值时。

if (email.value === confirmEmail.value) {
  // clear custom error for all related controls
  this.setFieldAsValid(email);
  this.setFieldAsValid(confirmEmail);
  return null;
} else {
  this.setFieldAsInvalid(email);
  this.setFieldAsInvalid(confirmEmail);
  return { nomatch: true };
}


private setFieldAsValid(field: AbstractControl) {
  field.setErrors(null);
}

参见固定 demo.


解决方案2: 如果你只关心错误 nomatch,你可以在表单组 data 上跟踪它是否有如下所示的 nomatch 错误(也包含在上面的演示模板部分):

this.user.get('data').hasError('nomatch')     

注意这种方式会让其他的formcontrol失效。

我使用反应式方法创建了一个示例表单,其中我使用了 built-in 验证器和自定义验证器。

打字稿:

export class AppComponent  {

  forbiddenEmails = ['test@test.com'];

  loginForm = new FormGroup({
    'name': new FormControl(null, [Validators.required]),
    'email': new FormControl(null, [Validators.required, Validators.email, this.customEmailValidator.bind(this)])
  });

  onSubmit(){
    console.log(this.loginForm.get('email').value);
  }

  customEmailValidator(control: FormControl): {[params: string]: boolean}{
    console.log(this.forbiddenEmails.indexOf(control.value));
    if(this.forbiddenEmails.indexOf(control.value) === 0){
      return {'forbiddenEmail': true};
    }else{
       return null;
    }
  }
}

模板:

   <h3>Basic Form</h3>
    <form class="form-horizontal"  [formGroup]='loginForm' (ngSubmit)="onSubmit()">
        <div class="form-group">
            <label for="name" class="col-sm-3 control-label">Name:</label>
            <div class="col-sm-6">
                <input type="text" 
                id="name" 
                class="form-control"
                formControlName='name' />
            </div>
            <div class="col-sm-3"></div>
            <span *ngIf='loginForm.get("name").invalid && loginForm.get("name").touched'>Pleas enter a valid name !!!</span>
        </div>
        <div class="form-group">
            <label for="email" class="col-sm-3 control-label">Email:</label>
            <div class="col-sm-6">
                <input type="email"
                 id="email"
                 class="form-control"
                 formControlName="email"/>
            </div>
            <div class="col-sm-3"></div>
            <span *ngIf='loginForm.get("email").invalid && loginForm.get("email").touched'>Pleas enter a valid email !!!</span>
        </div>
        <hr>
        <button class="btn btn-primary" 
        type="submit"
        [disabled]='!loginForm.valid'
        >Submit</button>
    </form>

Link: https://stackblitz.com/edit/angular-nwahpa?file=app%2Fapp.component.html