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?
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.
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
}
});
}
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
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 !