繁体   English   中英

反应性 Angular 表单等待异步验证器在提交时完成

[英]Reactive Angular form to wait for async validator complete on submit

我正在构建一个反应式角度表单,我正在尝试找到一种方法来触发所有验证器提交。 如果验证器是同步的,那就没问题了,因为我可以在线获取它的状态。 否则,如果验证器是异步验证器并且尚未触发,则ngSubmit方法上的表单将处于挂起状态。 我试图注册表单statusChange属性的订阅,但是当我使用markAsTouched函数markAsTouched调用验证时它没有被触发。

以下是一些片段:

   //initialization of form and watching for statusChanges
   ngOnInit() {
        this.ctrlForm = new FormGroup({
            'nome': new FormControl('', Validators.required),
            'razao_social': new FormControl('', [], CustomValidators.uniqueName),
            'cnpj': new FormControl('', CustomValidators.cnpj),
        });

        this.ctrlForm.statusChanges.subscribe(
            x => console.log('Observer got a next value: ' + x),
            err => console.error('Observer got an error: ' + err),
            () => console.log('Observer got a complete notification')
        )
    }
    //called on ngSubmit
    register(ctrlForm: NgForm) {
            Forms.validateAllFormFields(this.ctrlForm);
            console.log(ctrlForm.pending); 
            //above will be true if the async validator
            //CustomValidators.uniqueName was not called during form fill.
    }
    //iterates on controls and call markAsTouched for validation,
    //which doesn't fire statusChanges
    validateAllFormFields(formGroup: FormGroup) {         
          Object.keys(formGroup.controls).forEach(field => {  
              const control = formGroup.get(field);             
              if (control instanceof FormControl) {             
                control.markAsTouched({ onlySelf: true });
              } else if (control instanceof FormGroup) {        
                this.validateAllFormFields(control);            
              }
          });
      }

关于如何确保异步验证器已执行以便我可以继续触发并完成所有验证器的寄存器逻辑的任何想法?

Angular 在触发ngSubmit之前不会等待异步验证器完成。 因此,如果验证器尚未解析,则表单可能无效。

使用Subject来发出表单提交,您可以将switchMapform.statusChangefilter结果。

如果表单在提交时有效, startWith开始以确保没有挂起的排放。

通过PENDING过滤等待此状态更改,而take(1)确保流在挂起后的第一次发射时完成: VALIDINVALID

//
// <form (ngSubmit)="formSubmitSubject$.next()">

this.formSubmitSubject$ = new Subject();

this.formSubmitSubject$
  .pipe(
    tap(() => this.form.markAsDirty()),
    switchMap(() =>
      this.form.statusChanges.pipe(
        startWith(this.form.status),
        filter(status => status !== 'PENDING'),
        take(1)
      )
    ),
    filter(status => status === 'VALID')
  )
  .subscribe(validationSuccessful => this.submitForm());

您还可以添加一个触发将表单设置为脏的副作用的tap

在继续提交表单之前,使用formGroup.statusChanges等待 asyncValidators 完成。 如果 asyncValidators 没有错误,则继续提交。 另一方面,如果失败,请不要提交。 您的表单应该已经处理失败的验证器。 如果您不再需要订阅,请记住取消订阅。

 if (this.formGroup.pending) {
      let sub = this.formGroup.statusChanges.subscribe((res) => {
        if (this.formGroup.valid) {
          this.submit();
        }
        sub.unsubscribe();
      });
    } else {
      this.submit();
    }

markAsTouched不会触发验证,而是使用markAsDirty ,然后您的自定义验证器将触发。 所以换...

control.markAsTouched({ onlySelf: true });

 control.markAsDirty({ onlySelf: true });

此外,如果您使用的是 v 5,则可以使用可选的updateOn: 'submit' ,它在提交表单之前不会更新值(因此不会更新验证)。 为此,请进行以下更改:

this.ctrlForm = new FormGroup({
  'nome': new FormControl('', Validators.required),
  'razao_social': new FormControl('', [], CustomValidators.uniqueName),
  'cnpj': new FormControl('', CustomValidators.cnpj),
}, { updateOn: 'submit' }); // add this!

有了这个,这意味着您不再需要调用this.validateAllFormFields(control) ,我假设它会切换一些布尔标志并检查验证或类似的东西。

这是一个表单示例,提交表单后总是返回错误:

https://stackblitz.com/edit/angular-rjnfbv?file=app/app.component.ts

我刚刚在我的应用程序中实现了这个版本,它手动调用每个控件同步和异步验证器并返回一个布尔值,指示是否所有验证都通过

checkIfFormPassesValidation(formGroup: FormGroup) {
    const syncValidationErrors = Object.keys(formGroup.controls).map(c => {
      const control = formGroup.controls[c];
      return !control.validator ? null : control.validator(control);
    }).filter(errors => !!errors);
    return combineLatest(Object.keys(formGroup.controls).map(c => {
      const control = formGroup.controls[c];
      return !control.asyncValidator ? of(null) : control.asyncValidator(control)
    })).pipe(
      map(asyncValidationErrors => {
        const hasErrors = [...syncValidationErrors, ...asyncValidationErrors.filter(errors => !!errors)].length;
        if (hasErrors) { // ensure errors display in UI...
          Object.keys(formGroup.controls).forEach(key => {
            formGroup.controls[key].markAsTouched();
            formGroup.controls[key].updateValueAndValidity();
          })
        }
        return !hasErrors;
      })).toPromise();
  }

用法:

onSubmitForm() {
  checkIfFormPassesValidation(this.formGroup)
    .then(valid => {
      if (valid) {
        // proceed
      }
    });
}

如果我得到一个FormGroup类的form (反应式表单),我会使用AbstractControl/Property/valid来检查表单是否有效,然后再继续将其发送到服务器。

我使用的异步验证器必须返回=> Promise<ValidationErrors | null> => Promise<ValidationErrors | null>更改表单字段后表单再次有效之前 如果谷歌没有这样设计它会很奇怪......但他们做到了!

反应式表单验证

在 angular https://github.com/angular/angular/issues/31021 的这个问题中还有一个作为指令实现的解决方案

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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