Obtenez toutes les erreurs de validation de Angular 2 FormGroup


90

Compte tenu de ce code:

this.form = this.formBuilder.group({
      email: ['', [Validators.required, EmailValidator.isValid]],
      hasAcceptedTerms: [false, Validators.pattern('true')]
    });

Comment puis-je obtenir toutes les erreurs de validation this.form?

J'écris des tests unitaires et je souhaite inclure les erreurs de validation réelles dans le message d'assert.


Au lieu de Validators.pattern ('true'), vous pouvez / devriez utiliser Validators.requiredTrue pour appliquer la case à cocher.
Annulé

Réponses:


141

J'ai rencontré le même problème et pour trouver toutes les erreurs de validation et les afficher, j'ai écrit la méthode suivante:

getFormValidationErrors() {
  Object.keys(this.productForm.controls).forEach(key => {

  const controlErrors: ValidationErrors = this.productForm.get(key).errors;
  if (controlErrors != null) {
        Object.keys(controlErrors).forEach(keyError => {
          console.log('Key control: ' + key + ', keyError: ' + keyError + ', err value: ', controlErrors[keyError]);
        });
      }
    });
  }

Le nom du formulaire productFormdoit être remplacé par le vôtre.

Cela fonctionne de la manière suivante: nous obtenons tous nos contrôles du formulaire au format {[p: string]: AbstractControl}et itérons par chaque clé d'erreur, pour obtenir les détails de l'erreur. Il ignore nullles valeurs d'erreur.

Il peut également être modifié pour afficher les erreurs de validation sur la vue du modèle, il suffit de le remplacer console.log(..)par ce dont vous avez besoin.


2
Comment étendre la méthode ci-dessus pour FormArray dans le même modèle?
Mohammad Sharaf Ali

Voulez-vous dire ' + controlErrors[keyErrors];au lieu de ', controlErrors[keyErrors];?
ryanm

@ryanm non, il y a différent dans l'impression comme objet, ou comme valeur de chaîne.
Alex Efimov

d'où puis-je importer ValidationErrorsdans angular 2?
sainu

import { ValidationErrors } from '@angular/forms';
Craig Wayne le

31

C'est une solution avec des FormGroupsupports intérieurs ( comme ici )

Testé sur: Angular 4.3.6

get-form-validation-errors.ts

import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms';

export interface AllValidationErrors {
  control_name: string;
  error_name: string;
  error_value: any;
}

export interface FormGroupControls {
  [key: string]: AbstractControl;
}

export function getFormValidationErrors(controls: FormGroupControls): AllValidationErrors[] {
  let errors: AllValidationErrors[] = [];
  Object.keys(controls).forEach(key => {
    const control = controls[ key ];
    if (control instanceof FormGroup) {
      errors = errors.concat(getFormValidationErrors(control.controls));
    }
    const controlErrors: ValidationErrors = controls[ key ].errors;
    if (controlErrors !== null) {
      Object.keys(controlErrors).forEach(keyError => {
        errors.push({
          control_name: key,
          error_name: keyError,
          error_value: controlErrors[ keyError ]
        });
      });
    }
  });
  return errors;
}

En utilisant l'exemple :

if (!this.formValid()) {
  const error: AllValidationErrors = getFormValidationErrors(this.regForm.controls).shift();
  if (error) {
    let text;
    switch (error.error_name) {
      case 'required': text = `${error.control_name} is required!`; break;
      case 'pattern': text = `${error.control_name} has wrong pattern!`; break;
      case 'email': text = `${error.control_name} has wrong email format!`; break;
      case 'minlength': text = `${error.control_name} has wrong length! Required length: ${error.error_value.requiredLength}`; break;
      case 'areEqual': text = `${error.control_name} must be equal!`; break;
      default: text = `${error.control_name}: ${error.error_name}: ${error.error_value}`;
    }
    this.error = text;
  }
  return;
}

1
Changement angulaire 5 - const controlErrors: ValidationErrors = form.controls [key] .errors;
Kris Kilton

Suggestion de vérifier la véracité sur controlErrors c.- à - if (controlErrors) {d. Que la vérification uniquement nulldonnera une erreur si des erreurs sontundefined
mtholen le

8

Ceci est une autre variante qui collecte les erreurs de manière récursive et ne dépend d'aucune bibliothèque externe comme lodash(ES6 uniquement):

function isFormGroup(control: AbstractControl): control is FormGroup {
  return !!(<FormGroup>control).controls;
}

function collectErrors(control: AbstractControl): any | null {
  if (isFormGroup(control)) {
    return Object.entries(control.controls)
      .reduce(
        (acc, [key, childControl]) => {
          const childErrors = collectErrors(childControl);
          if (childErrors) {
            acc = {...acc, [key]: childErrors};
          }
          return acc;
        },
        null
      );
  } else {
    return control.errors;
  }
}

6

Méthode récursive pour récupérer toutes les erreurs d'une forme angulaire , après avoir créé tout type de structure de formulaire, il n'y a aucun moyen de récupérer toutes les erreurs du formulaire. Ceci est très utile à des fins de débogage mais aussi pour tracer ces erreurs.

Testé pour Angular 9

getFormErrors(form: AbstractControl) {
    if (form instanceof FormControl) {
        // Return FormControl errors or null
        return form.errors ?? null;
    }
    if (form instanceof FormGroup) {
        const groupErrors = form.errors;
        // Form group can contain errors itself, in that case add'em
        const formErrors = groupErrors ? {groupErrors} : {};
        Object.keys(form.controls).forEach(key => {
            // Recursive call of the FormGroup fields
            const error = this.getFormErrors(form.get(key));
            if (error !== null) {
                // Only add error if not null
                formErrors[key] = error;
            }
        });
        // Return FormGroup errors or null
        return Object.keys(formErrors).length > 0 ? formErrors : null;
    }
}

J'utilise Angular 7 et j'ai apporté deux modifications à votre code: form.errors ?? nullj'ai dû supprimer le ?? pour qu'il compile. Plus important encore, dans la condition de vérification FormGroup, j'ai ajouté || formParameter instanceof FormArrayce qui a vraiment ouvert mon application. Merci!
Tyler Forsythe

6

Ou vous pouvez simplement utiliser cette bibliothèque pour obtenir toutes les erreurs, même à partir de formulaires profonds et dynamiques.

npm i @naologic/forms

Si vous souhaitez utiliser la fonction statique sur vos propres formulaires

import {NaoFormStatic} from '@naologic/forms';
...
const errorsFlat = NaoFormStatic.getAllErrorsFlat(fg); 
console.log(errorsFlat);

Si vous souhaitez l'utiliser, NaoFromGroupvous pouvez l'importer et l'utiliser

import {NaoFormGroup, NaoFormControl, NaoValidators} from '@naologic/forms';
...
    this.naoFormGroup = new NaoFormGroup({
      firstName: new NaoFormControl('John'),
      lastName: new NaoFormControl('Doe'),
      ssn: new NaoFormControl('000 00 0000', NaoValidators.isSSN()),
    });

   const getFormErrors = this.naoFormGroup.getAllErrors();
   console.log(getFormErrors);
   // --> {first: {ok: false, isSSN: false, actualValue: "000 00 0000"}}

Lire la documentation complète


2

Basé sur la réponse @MixerOID , voici ma solution finale en tant que composant (peut-être que je crée une bibliothèque). Je supporte également FormArray:

import {Component, ElementRef, Input, OnInit} from '@angular/core';
import {FormArray, FormGroup, ValidationErrors} from '@angular/forms';
import {TranslateService} from '@ngx-translate/core';

interface AllValidationErrors {
  controlName: string;
  errorName: string;
  errorValue: any;
}

@Component({
  selector: 'app-form-errors',
  templateUrl: './form-errors.component.html',
  styleUrls: ['./form-errors.component.scss']
})
export class FormErrorsComponent implements OnInit {

  @Input() form: FormGroup;
  @Input() formRef: ElementRef;
  @Input() messages: Array<any>;

  private errors: AllValidationErrors[];

  constructor(
    private translateService: TranslateService
  ) {
    this.errors = [];
    this.messages = [];
  }

  ngOnInit() {
    this.form.valueChanges.subscribe(() => {
      this.errors = [];
      this.calculateErrors(this.form);
    });

    this.calculateErrors(this.form);
  }

  calculateErrors(form: FormGroup | FormArray) {
    Object.keys(form.controls).forEach(field => {
      const control = form.get(field);
      if (control instanceof FormGroup || control instanceof FormArray) {
        this.errors = this.errors.concat(this.calculateErrors(control));
        return;
      }

      const controlErrors: ValidationErrors = control.errors;
      if (controlErrors !== null) {
        Object.keys(controlErrors).forEach(keyError => {
          this.errors.push({
            controlName: field,
            errorName: keyError,
            errorValue: controlErrors[keyError]
          });
        });
      }
    });

    // This removes duplicates
    this.errors = this.errors.filter((error, index, self) => self.findIndex(t => {
      return t.controlName === error.controlName && t.errorName === error.errorName;
    }) === index);
    return this.errors;
  }

  getErrorMessage(error) {
    switch (error.errorName) {
      case 'required':
        return this.translateService.instant('mustFill') + ' ' + this.messages[error.controlName];
      default:
        return 'unknown error ' + error.errorName;
    }
  }
}

Et le HTML:

<div *ngIf="formRef.submitted">
  <div *ngFor="let error of errors" class="text-danger">
    {{getErrorMessage(error)}}
  </div>
</div>

Usage:

<app-form-errors [form]="languageForm"
                 [formRef]="formRef"
                 [messages]="{language: 'Language'}">
</app-form-errors>

2

Essayez ceci, cela appellera la validation pour tous les contrôles sous forme:

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

1
export class GenericValidator {
    constructor(private validationMessages: { [key: string]: { [key: string]: string } }) {
    }

processMessages(container: FormGroup): { [key: string]: string } {
    const messages = {};
    for (const controlKey in container.controls) {
        if (container.controls.hasOwnProperty(controlKey)) {
            const c = container.controls[controlKey];
            if (c instanceof FormGroup) {
                const childMessages = this.processMessages(c);
                // handling formGroup errors messages
                const formGroupErrors = {};
                if (this.validationMessages[controlKey]) {
                    formGroupErrors[controlKey] = '';
                    if (c.errors) {
                        Object.keys(c.errors).map((messageKey) => {
                            if (this.validationMessages[controlKey][messageKey]) {
                                formGroupErrors[controlKey] += this.validationMessages[controlKey][messageKey] + ' ';
                            }
                        })
                    }
                }
                Object.assign(messages, childMessages, formGroupErrors);
            } else {
                // handling control fields errors messages
                if (this.validationMessages[controlKey]) {
                    messages[controlKey] = '';
                    if ((c.dirty || c.touched) && c.errors) {
                        Object.keys(c.errors).map((messageKey) => {
                            if (this.validationMessages[controlKey][messageKey]) {
                                messages[controlKey] += this.validationMessages[controlKey][messageKey] + ' ';
                            }
                        })
                    }
                }
            }
        }
    }
    return messages;
}
}

Je l'ai pris à Deborahk et l' ai modifié un peu.


1
// IF not populated correctly - you could get aggregated FormGroup errors object
let getErrors = (formGroup: FormGroup, errors: any = {}) {
  Object.keys(formGroup.controls).forEach(field => {
    const control = formGroup.get(field);
    if (control instanceof FormControl) {
      errors[field] = control.errors;
    } else if (control instanceof FormGroup) {
      errors[field] = this.getErrors(control);
    }
  });
  return errors;
}

// Calling it:
let formErrors = getErrors(this.form);

0

Vous pouvez parcourir la propriété this.form.errors.


14
Je suppose que cela this.form.errorsne renvoie que des erreurs de validation pour le this.form, pas pour this.form.controls. Vous pouvez valider FormGroups et ses enfants (nombre arbitraire de FormGroups, FormControls et FormArrays) séparément. Pour récupérer toutes les erreurs, je pense que vous devez leur demander de manière récursive.
Risto Välimäki

0

Pour une grande arborescence FormGroup, vous pouvez utiliser lodash pour nettoyer l'arborescence et obtenir une arborescence contenant uniquement les contrôles contenant des erreurs. Cela se fait en répétant via des contrôles enfants (par exemple en utilisant allErrors(formGroup)) et en élaguant tous les sous-groupes de contrôles pleinement valides:

private isFormGroup(control: AbstractControl): control is FormGroup {
  return !!(<FormGroup>control).controls;
}

// Returns a tree of any errors in control and children of control
allErrors(control: AbstractControl): any {
  if (this.isFormGroup(control)) {
    const childErrors = _.mapValues(control.controls, (childControl) => {
      return this.allErrors(childControl);
    });

    const pruned = _.omitBy(childErrors, _.isEmpty);
    return _.isEmpty(pruned) ? null : pruned;
  } else {
    return control.errors;
  }
}

-2

J'utilise angular 5 et vous pouvez simplement vérifier la propriété status de votre formulaire en utilisant FormGroup par exemple

this.form = new FormGroup({
      firstName: new FormControl('', [Validators.required, validateName]),
      lastName: new FormControl('', [Validators.required, validateName]),
      email: new FormControl('', [Validators.required, validateEmail]),
      dob: new FormControl('', [Validators.required, validateDate])
    });

this.form.status serait "INVALID" à moins que tous les champs passent toutes les règles de validation.

La meilleure partie est qu'il détecte les changements en temps réel.


1
oui mais nous devons obtenir les erreurs de tout un groupe de formulaires, pas seulement savoir si ce n'est pas valide
Motassem MK

L'OP a besoin des messages de validation, qui ne sont pas inclus dans la propriété status, car il s'agit simplement d'un booléen.
Stefan
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.