Obtenga todos los errores de validación de Angular 2 FormGroup

90

Dado este código:

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

¿Cómo puedo obtener todos los errores de validación this.form?

Estoy escribiendo pruebas unitarias y quiero incluir los errores de validación reales en el mensaje de confirmación.

EagleBeak
fuente
En lugar de Validators.pattern ('true'), podría / debería usar Validators.requiredTrue para hacer cumplir la casilla de verificación.
Anulado el

Respuestas:

141

Encontré el mismo problema y para encontrar todos los errores de validación y mostrarlos, escribí el siguiente método:

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

El nombre del formulario productFormdebe cambiarse por el suyo.

Funciona de la siguiente manera: obtenemos todos nuestros controles del formulario en formato {[p: string]: AbstractControl}e iteramos por cada clave de error, para obtener detalles del error. Omite nulllos valores de error.

También se puede cambiar para mostrar errores de validación en la vista de plantilla, simplemente reemplácelo console.log(..)por lo que necesite.

Alex Efimov
fuente
2
¿Cómo extender el método anterior para FormArray en el mismo patrón?
Mohammad Sharaf Ali
¿Querías decir en ' + controlErrors[keyErrors];lugar de ', controlErrors[keyErrors];?
Ryanm
@ryanm no, hay diferencias en la impresión como objeto o como valor de cadena.
Alex Efimov
¿desde dónde puedo importar ValidationErrorsen angular 2?
sainu
import { ValidationErrors } from '@angular/forms';
Craig Wayne
31

Esta es una solución con FormGroupsoportes interiores ( como aquí )

Probado en: 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;
}

Ejemplo de uso :

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
fuente
1
Cambio de Angular 5 - const controlErrors: ValidationErrors = form.controls [key] .errors;
Kris Kilton
Sugerencia para verificar la veracidad en controlErrors ie, if (controlErrors) {ya que verificar solo nulldará un error si los errores sonundefined
mtholen
8

Esta es otra variante que recopila los errores de forma recursiva y no depende de ninguna biblioteca externa como lodash(solo ES6):

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
fuente
6

Una forma recursiva de recuperar todos los errores de un formulario angular , después de crear cualquier tipo de estructura de formulario, no hay forma de recuperar todos los errores del formulario. Esto es muy útil para fines de depuración, pero también para trazar esos errores.

Probado para 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
fuente
Estoy usando Angular 7 e hice dos modificaciones a su código: form.errors ?? nulltuve que eliminar el ?? para que se compile. Más importante aún, en la condición de verificación de FormGroup, agregué lo || formParameter instanceof FormArrayque realmente abrió mi aplicación. ¡Gracias!
Tyler Forsythe
6

O simplemente puede usar esta biblioteca para obtener todos los errores, incluso de formularios profundos y dinámicos.

npm i @naologic/forms

Si desea utilizar la función estática en sus propios formularios

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

Si quieres usarlo NaoFromGrouppuedes importarlo y usarlo

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"}}

Leer la documentación completa

Pian0_M4n
fuente
2

Basado en la respuesta de @MixerOID , aquí está mi solución final como componente (tal vez creo una biblioteca). También apoyo FormArray's:

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

Y el HTML:

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

Uso:

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

Intente esto, llamará a la validación para todos los controles en forma:

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
fuente
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;
}
}

Lo tomé de Deborahk y lo modifiqué un poco.

bangash
fuente
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);
uroslatos
fuente
0

Puede iterar sobre la propiedad this.form.errors.

unsafePtr
fuente
14
Supongo que this.form.errorssolo devuelve errores de validación para this.form, no para this.form.controls. Puede validar FormGroups y sus hijos (número arbitrario de FormGroups, FormControls y FormArrays) por separado. Para buscar todos los errores, creo que debe preguntarlos de forma recursiva.
Risto Välimäki
0

Para un árbol de FormGroup grande, puede usar lodash para limpiar el árbol y obtener un árbol de solo los controles con errores. Esto se hace recurriendo a controles secundarios (por ejemplo, usando allErrors(formGroup)) y podando cualquier subgrupo de controles completamente válido:

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
fuente
-2

Estoy usando angular 5 y simplemente puede verificar la propiedad de estado de su formulario usando FormGroup, por ejemplo

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 sería "INVALID" a menos que todos los campos pasen todas las reglas de validación.

La mejor parte es que detecta cambios en tiempo real.

Gagan
fuente
1
sí, pero necesitamos obtener los errores de un grupo de formularios completo, no solo saber si no es válido
Motassem MK
El OP necesita los mensajes de validación, que no están incluidos en la propiedad de estado, ya que es solo un booleano.
Stefan