148

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.

1
  • Instead of Validators.pattern('true') you could/should use Validators.requiredTrue to enforce checkbox being checked. Commented Mar 7, 2019 at 11:15

19 Answers 19

255

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.

Sign up to request clarification or add additional context in comments.

7 Comments

How to extend the above method for FormArray in the same pattern?
Did you mean ' + controlErrors[keyErrors]; instead of ', controlErrors[keyErrors];?
from where can I import ValidationErrors in angular 2?
import { ValidationErrors } from '@angular/forms';
Please note this does not work for nested FormGroups, I would suggest Mayur Dongre's method
|
45

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

2 Comments

Angular 5 change - const controlErrors: ValidationErrors = form.controls[key].errors;
Suggestion to check for truthy on controlErrors i.e. if (controlErrors) { as checking only for null will give an error if errors is undefined
17

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

3 Comments

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!
What about FormArray?
@greg93 replace the second condition with form instanceof FormGroup || form instanceof FormArray
12

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

Comments

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

Comments

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>

Comments

3

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

Comments

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

Comments

2

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

Comments

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

Comments

1

You can iterate over this.form.errors property.

1 Comment

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.
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.

Comments

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

Comments

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

Comments

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

Comments

0

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

Comments

0

Just for case you want to also show all particular error controls in form. Simply call this function to check for untouched errors before save the form:

checkErrors(): number {
  let hasError = 0;
  Object.keys(this.formGroup.controls).forEach(key => {
    const ctrlItem = this.formGroup.get(key);
    const controlErrors: ValidationErrors | null = ctrlItem ? ctrlItem.errors : null;

    if (controlErrors != null) {
      Object.keys(controlErrors).forEach(keyError => {
          hasError++;
      });
    }

    if (hasError) {
      this.formGroup.markAllAsTouched();
    }
  });
  return hasError;
}

Of course, you need to have a valid error snipped added in each control input

<mat-form-field>
    <mat-label>Text input</mat-label>
    <input name="textInput" matInput formControlName="textInput">
    <mat-error *ngIf="FormGroup.controls.textInput.errors">TextInput cannot be empty</mat-error>
</mat-form-field>

If any errors have been found, a error message is also displayed for each element of the form have an error.

Comments

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


export function getFormValidationErrors(control: AbstractControl | FormGroup | FormArray, errors: AllValidationErrors[] = [], name: string = null): AllValidationErrors[] {

    if (control instanceof FormControl) {
        if (control.errors !== null) {
            return Object.entries<{ [key: string]: string }>(control.errors)
                .reduce<AllValidationErrors[]>(
                    (acc, [errorKey, errorValue]) =>
                        [...acc, {controlName: name, errorName: errorKey, errorValue: errorValue}], errors)
        }
    } else if(control instanceof FormGroup) {
        return Object.entries(control.controls).reduce<AllValidationErrors[]>((acc, [name, control]) => getFormValidationErrors(control, acc, name), errors);
    } else if(control instanceof FormArray) {
        return control.controls.reduce<AllValidationErrors[]>((acc, control, index) => getFormValidationErrors(control, acc, `${name}[${index}]`), errors);
    }
}

Automation

it('Should collect all the form error into single list', () => {
        const formGroup = new FormGroup({
            name: new FormControl(null, [Validators.required]),
            address: new FormGroup({
                street: new FormControl(null, [Validators.required]),
            }),
            list: new FormArray([
                new FormGroup({
                    test: new FormControl(null, [Validators.required]),
                    innerList: new FormArray([
                        new FormGroup({
                            test3: new FormControl(null, [Validators.required])
                        })
                    ]),
                    innerList2: new FormArray([
                       new FormControl(null, [Validators.required]),
                    ]),
                })
            ]),

            list2: new FormArray([
                new FormControl(null, [Validators.required])
            ]),
        });

        const result: AllValidationErrors[] = getFormValidationErrors(formGroup);
        console.table(result)
        const expected: AllValidationErrors[] = [
            { controlName: 'name', errorName: 'required', errorValue: true },
            { controlName: 'street', errorName: 'required', errorValue: true },
            { controlName: 'test', errorName: 'required', errorValue: true },
            { controlName: 'list2[0]', errorName: 'required', errorValue: true }
        ];

        expect(result).toEqual(expected);
    });

Console output

1 Comment

This code-only answer could be improved with an explanation of how it implements the desired behaviour differently from the existing answers.
-4

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.

2 Comments

yes but we need to get the errors of an entire formgroup, not just know if its not valid
The OP needs the validation messages, which are not included in the status property, as it is just a boolean.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.