在提交

时间:2018-03-27 14:49:43

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

我正在构建一个反应式角形式,我正试图找到一种方法来触发提交时的所有验证器。如果验证者是同步的,那就没关系,因为我可以获得内联的状态。否则,如果验证器是异步的并且尚未触发,则ngSubmit方法上的表单将处于挂起状态。我已尝试注册表单statusChange属性的订阅,但是当我使用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);            
              }
          });
      }

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

6 个答案:

答案 0 :(得分:7)

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来触发将表单设置为脏表单的副作用。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

如果我得到了form类的FormGroup(反应形式),则在继续将其发送到服务器之前,我会使用AbstractControl/Property/valid检查该形式是否有效。 / p>

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

Reactive Form Validation

答案 3 :(得分:0)

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

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 :(得分:0)

在本期中,也有一个解决方案作为指令以角度https://github.com/angular/angular/issues/31021

实现。

答案 5 :(得分:0)

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