【问题标题】:Reactive Angular form to wait for async validator complete on submit响应式 Angular 表单等待异步验证器在提交时完成
【发布时间】:2018-09-06 01:13:56
【问题描述】:

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

这里有一些sn-ps:

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

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

【问题讨论】:

  • 尝试使用 this.roleForm.get("razao_social").setAsyncValidators([CustomValidators.uniqueName]) 来分配验证器
  • 为什么不创建一个返回可观察对象的 customValidator,并在注册时调用 CustomValidator(ctrlForm.value).subscribe(res=>{if (res.ok) continue else showError)?
  • @Eliseo,确实,它会解决问题,我没有想过这个,但我希望有一个更自动化的解决方案,我不需要知道 ngSubmit 上的验证器,使用 markAsTouched 或类似的
  • @Ricardo 我不明白它如何帮助我检索 register 方法的状态,如果你能给我一些想法......
  • @iangoop 一旦字段被修改,自定义验证器的想法就会被执行。如果你的验证器不工作是因为两个原因:错误的实现或错误的字段分配,我给出的解决方案您的分配错误,只需尝试测试至少该方法是否已被调用

标签: javascript angular angular-reactive-forms angular-validation


【解决方案1】:

使用 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();
    }

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 不错的解决方案,但如果您的异步验证器繁重或运行时间过长,可能会引入延迟。
      【解决方案4】:

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

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

      Reactive Form Validation

      【讨论】:

        【解决方案5】:

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

        使用Subject 发出表单提交,您可以将switchMap 发送到form.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 来触发将表单设置为脏的副作用。

        【讨论】:

        • 非常感谢! rxjs 相对较新,这正是我所需要的。 =)
        【解决方案6】:

        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

        【讨论】:

        • 对不起,我之前没有回答,我正忙于另一个项目,所以我不能做更多的测试。我不想使用updateOn: 'submit',因为我希望在输入更改时进行验证。我只考虑用户没有通过特定异步验证并且我想确保它在提交函数上执行的情况。我试图观察状态变化,在这种情况下,从PENDINGVALID,但即使从markAsTouched 变为markAsDirty,如果调用我的this.validateAllFormFields(control),它也不会触发事件
        • 我正在使用基于指令方式的异步。我遇到了同样的问题,因为我的提交发生在我的异步完成之前。将控件设置为脏可防止这种不可接受的提交。
        猜你喜欢
        • 2018-10-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-17
        • 1970-01-01
        相关资源
        最近更新 更多