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.

EagleBeak
la source
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.

Alex Efimov
la source
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;
}
MixerOID
la source
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;
  }
}
Andreas Klöber
la source
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;
    }
}
ArnauTG
la source
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

Pian0_M4n
la source
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>
ismaestro
la source
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);            
    }
  });
}
Mayur Dongre
la source
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.

Bangash
la source
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);
uroslates
la source
0

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

unsafePtr
la source
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;
  }
}
Olex Ponomarenko
la source
-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.

Gagan
la source
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