139

Given this code:

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

How can I get all validation errors from this.form?

I'm writing unit tests and want to include the actual validation errors in the assert message.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
EagleBeak
  • 6,939
  • 8
  • 31
  • 47
  • Instead of Validators.pattern('true') you could/should use Validators.requiredTrue to enforce checkbox being checked. – Void Mar 07 '19 at 11:15

17 Answers17

239

I met the same problem and for finding all validation errors and displaying them, I wrote this method:

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

Form name productForm should be changed to your form instance name.

It works in this way: we get all our controls from the form in format {[p: string]: AbstractControl} and iterate by each error key, to get details of error. It skips null error values.

It also can be changed for displaying validation errors on the template view, just replace console.log(..) to what you need.

Drazisil
  • 3,070
  • 4
  • 33
  • 53
Oleksandr Yefymov
  • 6,081
  • 2
  • 22
  • 32
42

This is solution with FormGroup inside supports ( like here )

Tested on: 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;
}

Using example:

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;
}
mixalbl4
  • 3,507
  • 1
  • 30
  • 44
  • 2
    Angular 5 change - const controlErrors: ValidationErrors = form.controls[key].errors; – Kris Kilton Jul 31 '18 at 22:23
  • Suggestion to check for truthy on `controlErrors` i.e. `if (controlErrors) {` as checking only for `null` will give an error if errors is `undefined` – mtholen Jul 09 '20 at 00:49
15

Recursive way to retrieve all the errors from an Angular form, after creating any kind of formulary structure there's no way to retrieve all the errors from the form. This is very useful for debugging purposes but also for plotting those errors.

Tested for 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
  • 661
  • 6
  • 10
  • 1
    I'm using Angular 7 and made two modifications to your code: `form.errors ?? null` I had to remove the ?? for it to compile. More importantly, in the FormGroup check condition, I added `|| formParameter instanceof FormArray` which really opened up my application. Thanks! – Tyler Forsythe Nov 09 '20 at 20:40
  • 1
    What about FormArray? – greg93 Jul 07 '21 at 09:36
  • 2
    @greg93 replace the second condition with `form instanceof FormGroup || form instanceof FormArray` – clemens Feb 14 '22 at 14:47
11

This is another variant that collects the errors recursively and does not depend on any external library like lodash (ES6 only):

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
  • 5,855
  • 2
  • 27
  • 20
7

Or you can just use this library to get all errors, even from deep and dynamic forms.

npm i @naologic/forms

If you want to use the static function on your own forms

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

If you want to use NaoFromGroup you can import and use it

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

Read the full documentation

Pian0_M4n
  • 2,505
  • 31
  • 35
4

Based on the @MixerOID response, here is my final solution as a component (maybe I create a library). I also support 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;
    }
  }
}

And the 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
  • 7,561
  • 8
  • 37
  • 50
2

Try This , it will call validation for all control in form :

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);            
    }
  });
}
Anand Rockzz
  • 6,072
  • 5
  • 64
  • 71
Mayur Dongre
  • 123
  • 7
2

For whom it might concern - I tweaked around with Andreas code in order to get all errors code in a flat object for easier logging errors that might appear.

Please consider:

export function collectErrors(control: AbstractControl): any | null {
  let errors = {};
  let recursiveFunc = (control: AbstractControl) => {
    if (isFormGroup(control)) {
      return Object.entries(control.controls).reduce(
        (acc, [key, childControl]) => {
          const childErrors = recursiveFunc(childControl);
          if (childErrors) {
            if (!isFormGroup(childControl)) {
              errors = { ...errors, [key]: childErrors };
            }
            acc = { ...acc, [key]: childErrors };
          }
          return acc;
        },
        null
      );
    } else {
      return control.errors;
    }
  };
  recursiveFunc(control);
  return errors;
}
Dinaiscoding
  • 992
  • 1
  • 7
  • 16
2

Adapting the accepted answer to return a string that can then be printed to console:

function getFormValidationErrors(form: FormGroup): string {
    return Object.keys(form.controls)
        .map((control) => {
            const controlErrors = form.get(control).errors;
            if (!controlErrors) {
                return [];
            }
            const controlErrorsString = Object.keys(controlErrors)
                .flatMap(
                    (keyError) => `${keyError}: ${controlErrors[keyError]}`
                )
                .join(', ');
            return `${control}: {${controlErrorsString}}`;
        })
        .filter((list) => list.length > 0)
        .join('\n');
}
A.Wan
  • 1,818
  • 3
  • 21
  • 34
1

You can iterate over this.form.errors property.

unsafePtr
  • 1,591
  • 2
  • 17
  • 27
  • 14
    I guess that ```this.form.errors``` returns only errors of validation for the ```this.form```, not for ```this.form.controls```. You can validate FormGroups and its children (arbitrary number of FormGroups, FormControls and FormArrays) separately. To fetch all the errors, I think you need to ask them recursively. – Risto Välimäki Nov 30 '16 at 09:12
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;
}
}

I took it from Deborahk and modified it a little bit.

Rahul Sharma
  • 2,867
  • 2
  • 27
  • 40
bangash
  • 398
  • 1
  • 4
  • 12
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
  • 479
  • 4
  • 2
1

I have got a requirement to present all errors of very complex FormGroup control which contains FormControls, FromGroups and FormArrays

i tried to fimd simple solution but I was not able to find the perfect solution which support all types of controls so i have develop the following simple recursive function and I am sharring with all:

export interface FieldError {
    formGroupName: string;
    fieldName: string;
    errorCode: string;
}

export function getFormErrors(
     control: AbstractControl, 
     formGroupName: string, 
     fieldName: string, 
     errors: FieldError[]) {

     if (control instanceof FormGroup) {
         Object.keys(control.controls).forEach(controlName => {
             let formControl = control.get(controlName);
             if (formControl) {
                 let fGroupName = formGroupName + "-" + controlName;
                 getFormErrors(formControl, fGroupName, controlName, errors);
             }
         })
     }

     if (control instanceof FormArray) {
         control.controls.forEach((fControl: AbstractControl, index) => {
             let fGroupName = formGroupName + "-" + index;
             getFormErrors(fControl, fGroupName, "Array", errors);
         })
     }

     if (control instanceof FormControl) {
         const controlErrors: ValidationErrors | null = control.errors;
         if (controlErrors) {
             Object.keys(controlErrors).forEach(errorCode => {
                 errors.push({
                     formGroupName: formGroupName,
                     fieldName: fieldName,
                     errorCode: errorCode
                 })
             });
         }
     }
 }

the usage is as follow:

    let errors: FieldError[] = []
    getFormErrors(YOUR_FORM_GROUP, "root", "", errors);
Zion Cohen
  • 182
  • 6
  • 19
1

Using inspiration from Arnautg's answer, I came up with this solution that will recursively combine all of the errors in the provided AbstractControl into a single ValidationErrors object.

function getFormErrors(form: AbstractControl): ValidationErrors | null {
  if (form instanceof FormControl) {
    return form.errors;
  }
  else if (form instanceof FormGroup || form instanceof FormArray) {
    let controlErrors: ValidationErrors = {};
    for (const control of Object.values(form.controls)) {
      controlErrors = {
        ...controlErrors,
        ...getFormErrors(control)  // Recursive call of the FormGroup fields
      };
    }
    // Return errors or null
    const errors: ValidationErrors = {
      ...form.errors,   // Form group/array can contain errors itself, in that case add them
      ...controlErrors,
    }
    return Object.keys(errors).length > 0 ? errors : null;
  }
  else {
    return null;
  }
}
0

For a large FormGroup tree, you can use lodash to clean up the tree and get a tree of just the controls with errors. This is done by recurring through child controls (e.g. using allErrors(formGroup)), and pruning any fully-valid sub groups of controls:

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
  • 905
  • 7
  • 10
0
**I met the same problem and for finding all validation errors and 
displaying only first error, I wrote next method:**

> first declare variable on top
  public errors: any = [];
  public fieldError: any = '';

> now subscribe form on noOnInit 
  
  this.form.valueChanges.subscribe(() => {
  this.showOnlyFirstError(this.form);
  this.errors = []
  });
  this.showOnlyFirstError(this.form);

> now call function

 showOnlyFirstError(form) {
 Object.keys(form.controls).forEach(key => {

 const controlErrors: ValidationErrors = form.get(key).errors;
 if (controlErrors != null) {
      Object.keys(controlErrors).forEach(keyError => {
        const showMessage = key + " is " + keyError
        this.errors.push(showMessage)
        this.fieldError = this.errors[0]
      });
     }
   });
 }
-3

I am using angular 5 and you can simply check the status property of your form using FormGroup e.g.

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 would be "INVALID" unless all the fields pass all the validation rules.

The best part is that it detects changes in real-time.

Gagan
  • 13