Get all validation errors from Angular 2 FormGroup

AngularTypescriptValidation

Angular Problem Overview


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.

Angular Solutions


Solution 1 - Angular

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.

Solution 2 - Angular

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

Solution 3 - Angular

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

Solution 4 - Angular

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

Solution 5 - Angular

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

Solution 6 - Angular

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>

Solution 7 - Angular

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

Solution 8 - Angular

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.

Solution 9 - Angular

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

Solution 10 - Angular

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

Solution 11 - Angular

You can iterate over this.form.errors property.

Solution 12 - Angular

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

Solution 13 - Angular

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

Solution 14 - Angular

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

Solution 15 - Angular

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

Solution 16 - Angular

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEagleBeakView Question on Stackoverflow
Solution 1 - AngularOleksandr YefymovView Answer on Stackoverflow
Solution 2 - Angularmixalbl4View Answer on Stackoverflow
Solution 3 - AngularArnautgView Answer on Stackoverflow
Solution 4 - AngularAndreas KlöberView Answer on Stackoverflow
Solution 5 - AngularPian0_M4nView Answer on Stackoverflow
Solution 6 - AngularismaestroView Answer on Stackoverflow
Solution 7 - AngularMayur DongreView Answer on Stackoverflow
Solution 8 - AngularbangashView Answer on Stackoverflow
Solution 9 - AngularuroslatesView Answer on Stackoverflow
Solution 10 - AngularDina FliesView Answer on Stackoverflow
Solution 11 - AngularunsafePtrView Answer on Stackoverflow
Solution 12 - AngularOlex PonomarenkoView Answer on Stackoverflow
Solution 13 - AngularSaurav MauryaView Answer on Stackoverflow
Solution 14 - AngularZion CohenView Answer on Stackoverflow
Solution 15 - AngularA.WanView Answer on Stackoverflow
Solution 16 - AngularGaganView Answer on Stackoverflow