How to find the invalid controls in Angular(v2 onwards) reactive form

AngularAngular Reactive-FormsReactive

Angular Problem Overview


I have a reactive form in Angular like below:

this.AddCustomerForm = this.formBuilder.group({
    Firstname: ['', Validators.required],
    Lastname: ['', Validators.required],
    Email: ['', Validators.required, Validators.pattern(this.EMAIL_REGEX)],
    Picture: [''],
    Username: ['', Validators.required],
    Password: ['', Validators.required],
    Address: ['', Validators.required],
    Postcode: ['', Validators.required],
    City: ['', Validators.required],
    Country: ['', Validators.required]
});

createCustomer(currentCustomer: Customer) 
{
    if (!this.AddCustomerForm.valid)
    {
        //some app logic
    }
}

this.AddCustomerForm.valid returns false, but everything looks good.

I have tried to find with checking the status property in the controls collection. But I wonder if there is a way to find the invalid ones and display to the user?

Angular Solutions


Solution 1 - Angular

You can simply iterate over every control and check the status:

    public findInvalidControls() {
		const invalid = [];
		const controls = this.AddCustomerForm.controls;
		for (const name in controls) {
			if (controls[name].invalid) {
				invalid.push(name);
			}
		}
		return invalid;
    }

Solution 2 - Angular

I just battled this issue: Every form field is valid, but still the form itself is invalid.

Turns out that I had set 'Validator.required' on a FormArray where controls are added/removed dynamically. So even if the FormArray was empty, it was still required and therefore the form was always invalid, even if every visible control was correctly filled.

I didn't find the invalid part of the form, because my 'findInvalidControls' function only checked FormControl's and not FormGroup/FormArray. So I updated it a bit:

/* 
   Returns an array of invalid control/group names, or a zero-length array if 
   no invalid controls/groups where found 
*/
public findInvalidControlsRecursive(formToInvestigate:FormGroup|FormArray):string[] {
    var invalidControls:string[] = [];
    let recursiveFunc = (form:FormGroup|FormArray) => {
      Object.keys(form.controls).forEach(field => { 
        const control = form.get(field);
        if (control.invalid) invalidControls.push(field);
        if (control instanceof FormGroup) {
          recursiveFunc(control);
        } else if (control instanceof FormArray) {
          recursiveFunc(control);
        }        
      });
    }
    recursiveFunc(formToInvestigate);
    return invalidControls;
  }

Solution 3 - Angular

An invalid Angular control has the CSS class named 'ng-invalid'.

Under DevTools in Chrome, select Console tab.

In console prompt run the following command in order to get the invalid Angular controls that bear the CSS class 'ng-invalid'

document.getElementsByClassName('ng-invalid')

The output should be similar to this: enter image description here

In this case, the underlined text is for the form control listen-address and the encircled text: .ng-invalid indicates that the control is invalid.

> Note: Tested in chrome

Solution 4 - Angular

Now, in angular 9, you can use the markAllAsTouched() method to show the invalid controls validators:

this.AddCustomerForm.markAllAsTouched();

Solution 5 - Angular

There exists an .error property on each the control of the reactive form. If this .error is set to true it indicates that a control is invalid. Thus, looping through the controls and checking this .error field will let us know which fields/ controls are invalid.

The below code will log all the invalid the controls 

for (let el in this.ReactiveForm.controls) {
      if (this.ReactiveForm.controls[el].errors) {
        console.log(el)
      }
 }			

One can alternatively append the field name to an array or a string and indicate the user which fields are invalid

Solution 6 - Angular

Both the forms and all your controls extend the angular class AbstractControl. Each implementation has an accessor to the validation errors.

let errors = this.AddCustomerForm.errors
// errors is an instance of ValidatorErrors

The api docs contains all the references https://angular.io/api/forms/AbstractControl

Edit

I thought the error accessor worked this way however this link to github shows that there are some other people who thought same as i did https://github.com/angular/angular/issues/11530

In any case, by using the controls accessor you can iterate over all formControls in your form.

Object.keys(this.AddCustomerForm.controls)
    .forEach( control => {
        //check each control here
        // if the child is a formGroup or a formArray
        // you may cast it and check it's subcontrols too
     })

Solution 7 - Angular

try this

 findInvalidControls(f: FormGroup) {
    const invalid = [];
    const controls = f.controls;
    for (const name in controls) {
      if (controls[name].invalid) {
        invalid.push(name);
      }
    }
    return invalid;
  }

Solution 8 - Angular

If you are not having much fields in the form, you can simply F12 and hover over the control, you will be able to see the pop-up with field's pristine/touched/valid values- "#fieldname.form-control.ng-untouched.ng-invalid".

Solution 9 - Angular

I took the liberty to improve AngularInDepth.com-s code, so that it recursively searches for invalid inputs in nested forms also. Wether it be nested by FormArray-s or FormGroup-s. Just input the top level formGroup and it will return all the FormControls that are invalid.

You can possibly skim some of the "instanceof" type checks away, if you would separate the FormControl check and addition to invalid array functionality into a separate function. This would make the function look a lot cleaner, but I needed a global, single function, option to get a flat array of all the invalid formControls and this is the solution!

findInvalidControls( _input: AbstractControl, _invalidControls: AbstractControl[] ): AbstractControl[] {
    if ( ! _invalidControls ) _invalidControls = [];
    if ( _input instanceof FormControl  ) {
        if ( _input.invalid ) _invalidControls.push( _input );
        return _invalidControls;
    }

    if ( ! (_input instanceof FormArray) && ! (_input instanceof FormGroup) ) return _invalidControls;

    const controls = _input.controls;
    for (const name in controls) {
        let control = controls[name];
        switch( control.constructor.name )
        {
            case 'AbstractControl':
            case 'FormControl':
                if (control.invalid) _invalidControls.push( control );
                break;

            case 'FormArray':
                (<FormArray> control ).controls.forEach( _control => _invalidControls = findInvalidControls( _control, _invalidControls ) );
                break;

            case 'FormGroup':
                _invalidControls = findInvalidControls( control, _invalidControls );
                break;
        }
    }

    return _invalidControls;
}

Just for those that need it, so they don't have to code it themselves..

Edit #1

It was requested that it also returns invalid FormArray-s and FormGroups, so if you need that also, use this code

findInvalidControls( _input: AbstractControl, _invalidControls: AbstractControl[] ): AbstractControl[] {
    if ( ! _invalidControls ) _invalidControls = [];
    if ( _input instanceof FormControl  ) {
        if ( _input.invalid ) _invalidControls.push( _input );
        return _invalidControls;
    }

    if ( ! (_input instanceof FormArray) && ! (_input instanceof FormGroup) ) return _invalidControls;

    const controls = _input.controls;
    for (const name in controls) {
        let control = controls[name];
        if (control.invalid) _invalidControls.push( control );
        switch( control.constructor.name )
        {    
            case 'FormArray':
                (<FormArray> control ).controls.forEach( _control => _invalidControls = findInvalidControls( _control, _invalidControls ) );
                break;

            case 'FormGroup':
                _invalidControls = findInvalidControls( control, _invalidControls );
                break;
        }
    }

    return _invalidControls;
}

Solution 10 - Angular

you can log value of form console.log(this.addCustomerForm.value), it will console all control's value then null or ""(empty) fields indicate invalid controls

Solution 11 - Angular

I think you should try using this.form.updateValueAndValidity() or try executing that same method in each of the controls.

Solution 12 - Angular

In my case, I had all form controls disabled.

It appears to be an open bug in Angular: https://github.com/angular/angular/issues/39287

Solution 13 - Angular

So I too have countered this dragon. And like the brave knight I am, I first gathered my weapons, read the maps and then fought this awful beast.

Note

This is not an acceptable answer for complex forms or structures, but I found it working for the easy ones without to many complexity

The code does the following:

  • Get the form controls as array
  • loop over and check if form control is invalid
  • if invalid the filter will INCLUDE it
  • if any was invalid, result array will be filled with that controls
  • if the length of this result is equal then 0, we can state that no controls were invalid and by that the whole form is valid
isFormValid = () :boolean => 
    Object.values(this.form.controls)
        .filter(c => c.invalid).length === 0

// or single lined
isFormValid = () :boolean => Object.values(this.form.controls).filter(c => c.invalid).length === 0

You can use it in the place you want, on submit buttons, onSubmit or on your own sweet spot.

Solution 14 - Angular

A cleaner and immutable recursive version of solution to above problem:

P.S: you will need both methods.

> Working tested uptill Angular 11

> In case compiler complains about flatMap, refer to this(https://stackoverflow.com/questions/53556409/typescript-flatmap-flat-flatten-doesnt-exist-on-type-any), and don't forger to restart ng serve

findInvalidControls(controls = this.defaultFormGroup.controls) {
    const ctrls = Object.values(controls);
    const names = Object.keys(controls);
    return ctrls.map((a,i) => [a, i])
      .filter(a => (a[0] as FormControl).invalid)
      .flatMap(a => {
        if (a[0] instanceof FormArray) {
          return this.findInvalidArrayControls(a[0].controls);
        } else if (a[0] instanceof FormGroup) {
          return this.findInvalidControls(a[0].controls);
        } else {
          return names[a[1] as number];
        }
      });
  }

  findInvalidArrayControls(controls: AbstractControl[]) {
    const ctrls = Object.values(controls);
    const names = Object.keys(controls);
    return ctrls.map((a,i) => [a, i])
      .filter(a => (a[0] as FormControl).invalid)
      .flatMap(a => {
        if (a[0] instanceof FormArray) {
          return this.findInvalidArrayControls(a[0].controls);
        } else if (a[0] instanceof FormGroup) {
          return this.findInvalidControls(a[0].controls);
        }
         else {
          return names[a[1] as number];
        }
     });
  }

Solution 15 - Angular

Create the flag

inProcess: boolean= false
   
   this.AddCustomerForm = this.formBuilder.group({
       Firstname: ['', Validators.required],
       Lastname: ['', Validators.required],
       Username: ['', Validators.required],
       Password: ['', Validators.required],
       Address: ['', Validators.required],
       Postcode: ['', Validators.required],
       City: ['', Validators.required],
       Country: ['', Validators.required]
   });

onSubmit()
{
   if(this.AddCustomerForm.invalid)
   {
     return
   }

  this.inProcess = true

// pass form value to restapi

}

and this inProcess flag you used in HTML form disable button

<button mat-button [disable]="inProcess"> ADD </button>

Once all form values are correct then only ADD button is visible

hope it will help u guys!!!

Solution 16 - Angular

For anybody who needs to filter formArray, to only have valid controls here's the solution;

const validControls = this.participantsDetails.controls.filter(control => control.valid);

and for invalid ones of course;

const validControls = this.participantsDetails.controls.filter(control => control.invalid);

Solution 17 - Angular

Check empty or null form control value in html page

Form controls value: {{formname.value | json}}

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
Questionsa_View Question on Stackoverflow
Solution 1 - AngularMax KoretskyiView Answer on Stackoverflow
Solution 2 - AngularJetteView Answer on Stackoverflow
Solution 3 - AngularMwizaView Answer on Stackoverflow
Solution 4 - AngularRodrigo Jorge BaptistaView Answer on Stackoverflow
Solution 5 - Angulartejas nView Answer on Stackoverflow
Solution 6 - AngularLookForAngularView Answer on Stackoverflow
Solution 7 - AngularTrilok SinghView Answer on Stackoverflow
Solution 8 - Angulardarshna mishraView Answer on Stackoverflow
Solution 9 - AngularKarl Johan VallnerView Answer on Stackoverflow
Solution 10 - AngularSohail AnwarView Answer on Stackoverflow
Solution 11 - AngularJavi MarzánView Answer on Stackoverflow
Solution 12 - AngularnovembreView Answer on Stackoverflow
Solution 13 - AngularNebulosarView Answer on Stackoverflow
Solution 14 - AngularRavinder PayalView Answer on Stackoverflow
Solution 15 - AngularSushilView Answer on Stackoverflow
Solution 16 - AngularMatko MilićView Answer on Stackoverflow
Solution 17 - AngularUttam KarView Answer on Stackoverflow