web-dev-qa-db-fra.com

Réactif Angular pour attendre la fin du validateur asynchrone lors de la soumission)

Je construis un réactif angular et j'essaie de trouver un moyen de déclencher tous les validateurs lors de la soumission. Si le valideur est un sync, ce serait ok, car je peux obtenir l'état de celui-ci en ligne. Sinon, si le validateur est asynchrone et qu'il n'a pas encore été déclenché, le formulaire sur la méthode ngSubmit serait en attente. J'ai essayé d'enregistrer un abonnement pour le formulaire statusChange, mais elle n'est pas déclenchée lorsque j'appelle à la validation manuellement avec la fonction markAsTouched.

Voici quelques extraits:

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

Toutes les idées sur comment puis-je m'assurer que le validateur asynchrone a été exécuté afin que je puisse continuer avec la logique de registre ayant tous les validateurs déclenchés et terminés?

11
iangoop

Angular n'attend pas la fin des validateurs asynchrones avant de lancer ngSubmit. Le formulaire peut donc être invalide si les validateurs ne sont pas résolus.

En utilisant un Subject pour émettre des soumissions de formulaire, vous pouvez switchMap vers form.statusChange Et filter les résultats.

Commencez par un startWith pour vous assurer qu'il n'y a pas d'émission suspendue, dans le cas où le formulaire est valide au moment de la soumission.

Le filtrage par PENDING attend que cet état change, et take(1) s'assure que le flux est terminé à la première émission après avoir été en attente: VALID ou 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());

Vous pouvez également ajouter un tap qui déclenche l'effet secondaire des paramètres du formulaire comme sale.

16
kyranjamie

Je viens d'implémenter une version de ceci dans mon application qui invoque manuellement tous les contrôles validateurs synchrones et asynchrones et retourne un booléen indiquant si toutes les validations sont passées:

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

Usage:

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

markAsTouched ne déclenchera pas la validation, utilisez à la place markAsDirty, puis votre validateur personnalisé se déclenchera. Alors changez ...

control.markAsTouched({ onlySelf: true });

à

 control.markAsDirty({ onlySelf: true });

De plus, si vous utilisez la version 5, vous pouvez utiliser l'option updateOn: 'submit' Facultative, qui ne mettra pas à jour les valeurs (et donc pas les validations) jusqu'à ce que le formulaire soit soumis. Pour cela, apportez les modifications suivantes:

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

Avec cela, cela signifie que vous n'avez plus besoin d'appeler this.validateAllFormFields(control), ce qui, je suppose, commute un indicateur booléen et vérifie la validation ou quelque chose comme ça.

Voici un exemple de formulaire qui renvoie toujours une erreur après l'envoi du formulaire:

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

0
AJT82

Si j'ai un form (formulaire réactif) avec la classe FormGroup, j'utilise AbstractControl/Property/valid pour vérifier si le formulaire est valide avant de continuer à envoyer à un serveur.

Le validateur asynchrone que j'utilise doit renvoyer => Promise<ValidationErrors | null> avant que le formulaire ne redevienne valide après une modification d'un champ de formulaire. Ce serait bizarre si Google ne le concevait pas comme ça ... Mais ils l'ont fait !

Validation du formulaire réactif

0
ravo10