122
votes

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?

15
Of you just want to display the fields with an error, you can use css to highlight or color the invalid fields. Each invalid field has an "ng-invalid" class appended in its class listLookForAngular
It's an open issue in Angular. You can give it a thumbs up to potentially raise the priority here: github.com/angular/angular/issues/10530Sandro
When formGroup.errors gives null and formGroup.valid gives false, it might be because of html tags like maxlength etc. They should not be mixedO-9

15 Answers

216
votes

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

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

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

Under DevTools in Chrome, select Console tab.

In console prompt type command:

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

8
votes

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

this.AddCustomerForm.markAllAsTouched();
4
votes

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
     })
3
votes

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

you can make an array or string out of this and display to the user

1
votes

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

1
votes

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

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

1
votes

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

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

0
votes

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

0
votes

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(Typescript flatMap, flat, flatten doesn't 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];
        }
     });
  }

0
votes

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.

-1
votes

Check empty or null form control value in html page