web-dev-qa-db-ja.com

リアクティブAngular送信時に非同期バリデーターが完了するのを待つフォーム

私はリアクティブangularフォームを作成していて、送信時にすべてのバリデーターをトリガーする方法を見つけようとしています。バリデーターが同期のものである場合は、それ以外の場合、バリデーターが非同期のものであり、まだトリガーされていない場合、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);            
              }
          });
      }

非同期バリデーターが確実に実行され、すべてのバリデーターがトリガーされて完了した状態でレジスターロジックを続行できるようにするためのアイデアはありますか?

11
iangoop

AngularはngSubmitを実行する前に非同期バリデーターが完了するのを待ちません。そのため、バリデーターが解決されていない場合、フォームは無効になる可能性があります。

Subjectを使用してフォーム送信を発行すると、結果をswitchMapから_form.statusChange_にfilterできます。

送信時にフォームが有効である場合は、startWithから始めて、ハンギングエミッションが発生しないようにします。

PENDINGによるフィルタリングは、このステータスが変更されるのを待ち、take(1)は、保留後の最初のエミッションでストリームが確実に完了するようにします:VALIDまたはINVALID

_//
// <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を追加することもできます。

16
kyranjamie

すべてのコントロールの同期および非同期バリデーターを手動で呼び出し、すべての検証がパスしたかどうかを示すブール値を返す

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
      }
    });
}
0
Stephen Paul

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

0
AJT82

クラスformFormGroup(リアクティブフォーム)を取得した場合、送信を続ける前に AbstractControl/Property/valid を使用してフォームが有効かどうかを確認しますそれをサーバーに。

私が使用する非同期バリデーターは、フォームフィールドが変更された後、=> Promise<ValidationErrors | null>フォームが再び有効になる前にを返す必要があります。 Googleがこのように設計していなかったら変だと思います...しかし、彼らは設計しました!

反応フォームの検証

0
ravo10