Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reactive Angular form to wait for async validator complete on submit

Tags:

I am building a reactive angular form and I'm trying to find a way to trigger all validators on submit. If the validor is a sync one, it'd be ok, as I can get the status of it inline. Otherwise, if the validator is an async one and it was not triggered yet, the form on ngSubmit method would be in pending status. I've tried to register a subscribe for the form statusChange property, but it's not triggered when I call for validation manualy with markAsTouched function.

Here's some snippets:

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

Any ideas on how can I ensure that the async validator was executed so I can continue with the register logic having all validators triggered and completed?

like image 528
iangoop Avatar asked Mar 27 '18 14:03

iangoop


People also ask

Are reactive forms asynchronous?

The Reactive Forms Module in Angular allows you to add synchronous and asynchronous validators to form elements. Synchronous validators of a form element always run when a keyup event happens on that element.

What is asynchronous validation in Angular?

Angular does not provide built-in type async Validation implmentation, it provides only for sync validation. The implementation of async validator is very similar to the sync validator. The only difference is that the async Validators must return the result of the validation as an observable or as Promise.

How do I add validation to reactive form?

In a reactive form, the source of truth is the component class. Instead of adding validators through attributes in the template, you add validator functions directly to the form control model in the component class. Angular then calls these functions whenever the value of the control changes.


2 Answers

Angular doesn't wait for async validators to complete before firing ngSubmit. So the form may be invalid if the validators have not resolved.

Using a Subject to emit form submissions, you can switchMap to form.statusChange and filter the results.

Begin with a startWith to ensure there's no hanging emission, in the case the form is valid at the time of submission.

Filtering by PENDING waits for this status to change, and take(1) makes sure the stream is completed on the first emission after pending: VALID or 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()); 

You can also add a tap that triggers the side effect of settings the form as dirty.

like image 105
kyranjamie Avatar answered Sep 17 '22 18:09

kyranjamie


Use formGroup.statusChanges to wait for asyncValidators to finish before proceed to submitting form. If the asyncValidators have no error, proceed to submit. On the other hand, if it fails, don't submit. Your form should already handle failed validators. Remember to unsubscribe the subscription if you no longer need it.

 if (this.formGroup.pending) {       let sub = this.formGroup.statusChanges.subscribe((res) => {         if (this.formGroup.valid) {           this.submit();         }         sub.unsubscribe();       });     } else {       this.submit();     } 
like image 45
PocoM Avatar answered Sep 18 '22 18:09

PocoM