181

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?

Ravinder Payal
  • 2,884
  • 31
  • 40
sa_
  • 2,854
  • 3
  • 22
  • 29
  • 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 list – LookForAngular Dec 21 '18 at 08:40
  • It's an open issue in Angular. You can give it a thumbs up to potentially raise the priority here: https://github.com/angular/angular/issues/10530 – Sandro Apr 15 '21 at 08:44
  • When formGroup.errors gives `null` and formGroup.valid gives false, it might be because of html tags like `maxlength` etc. They should not be mixed – O-9 Jun 09 '21 at 08:34
  • Regarding the open issue 10530 the TLDR; is that they aren't adding such functionality because it would increase the bundle size of all apps. Which I'm afraid is a pretty poor excuse given possible alternatives. – Simon_Weaver Nov 15 '22 at 20:11

20 Answers20

313

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;
    }
Akash Kumar Verma
  • 3,185
  • 2
  • 16
  • 32
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • 2
    thanks for this but I tried this and even this returns nothing my form is still invalid, this is weird. I mean this code looks good but it doesn't make any sense why form.valid returns false – sa_ Jul 20 '17 at 16:28
  • what does `findInvalidControls()` return you? – Max Koretskyi Jul 20 '17 at 16:29
  • 1
    it returns nothing, invalid is empty. I checked one by one in debug watch screen, all the controls are valid but this.AddCustomerForm.valid still returns false. – sa_ Jul 21 '17 at 15:01
  • I think I found out. there is an email field and regular expression but somehow the control's status is PENDING and it might be the cause – sa_ Jul 21 '17 at 15:11
  • @junky, yeah, it can be it :). Pending means that async validation is in progress – Max Koretskyi Jul 21 '17 at 15:13
  • I'm not sure it's a good idea but I changed the if line like this, if (controls[name].status != "VALID" { – sa_ Jul 21 '17 at 15:18
  • that's the [internal implementation](https://github.com/angular/angular/blob/master/packages/forms/src/model.ts#L126), no need to do that :) – Max Koretskyi Jul 21 '17 at 15:41
  • 9
    @AngularInDepth.com - if the one of the controls is a form group, your function will return the invalid form group and not the specific form control which is invalid – john Smith Mar 08 '18 at 14:04
  • In my case, none of the controls were invalid, but I realized that I disabled the form before checking if the form was invalid, so that produced that the form was invalid – ST7 Jul 01 '20 at 10:15
  • 1
    i had the same issue, that my form was invalid but all fields where valid. I updated the field via `field.setValue('1234', {onlySelf: true})`. As soon as i remove onlySelf:true it was working as expected! – djnose Nov 01 '20 at 10:44
  • could write this in shorter using ES6 features. `return controls.filter((control) => control.invalid).map((control) => control.name)` – Imants Volkovs Aug 27 '21 at 09:35
  • works perfect. Mine was a bit more advaned as I have formgroups in formgroups `let invalid = []; const controls = this.entryForm.get('entry') as FormGroup; for (const name in controls.controls) { if (controls.controls[name].invalid) { invalid.push(' ' +(+name + 1)); } }` – Johansrk Sep 28 '21 at 17:36
76

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

Mwiza
  • 7,780
  • 3
  • 46
  • 42
62

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;
  }
Jette
  • 2,459
  • 28
  • 37
  • 2
    Oh wow. Thanks a lot that saved me a lot of investigation-time! – Astrophage Feb 22 '21 at 15:23
  • I was battling the same issue, but even `findInvalidControlsRecursive` didn't find any invalid control. The problem was that I was replacing a form group like `form.controls.someFormGroup = updatedFormGroup;`. This mostly worked, but introduced suble issues with validation, etc. since the `parent` does not seem to get associated to the `FormGroup`. Using `removeControl`+`addControl` fixed it. – user2580621 Apr 27 '22 at 09:00
22

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

this.AddCustomerForm.markAllAsTouched();
17

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

tejas n
  • 638
  • 1
  • 8
  • 14
5

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
     })
LookForAngular
  • 1,030
  • 8
  • 18
  • 1
    this returns null even there are empty controls – sa_ Jul 20 '17 at 16:24
  • 1
    It should return null when there are no errors. Can you post your template? – LookForAngular Jul 20 '17 at 16:29
  • Yeah, this won't work, the different validations set on each form controls, those each form controls contain their errors, the form doesn't. You need to iterate the controls like Maximus have given answer. – AT82 Jul 20 '17 at 18:41
  • I can access errors for each individual contorls like this.form.controls['Email'].errors – Nazrul Muhaimin Dec 21 '18 at 08:01
  • @AJT_82 indeed the Form itself can show errors if a validator has been set for the formGroup ( check the docs about cross field validation, which makes sense to validate on the group and not in the control ) – LookForAngular Dec 21 '18 at 08:38
4

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;
  }
Trilok Singh
  • 1,227
  • 12
  • 10
4

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.

Nebulosar
  • 1,727
  • 3
  • 20
  • 46
2

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;
}
Karl Johan Vallner
  • 3,980
  • 4
  • 35
  • 46
  • 1
    I tried it, but it doesn't find any invalid FormGroup or FormArray... only invalid FormControl's. I made the same mistake... see my answer. – Jette Sep 13 '18 at 11:15
  • 1
    I improved my answer, to fit your use case. – Karl Johan Vallner Sep 13 '18 at 20:47
  • you can use this to literate over the controls: Object.entries(input.controls).forEach(([, control]) => { switch (control.constructor.name) { ... } }) – Milan Dec 09 '22 at 13:27
1

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

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

Sohail Anwar
  • 349
  • 3
  • 12
1

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

Dharman
  • 30,962
  • 25
  • 85
  • 135
Javi Marzán
  • 1,121
  • 16
  • 21
1

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

novembre
  • 529
  • 4
  • 9
  • Your case, can you explain if that fixed the problem or that it was the problem itself that you had the form controls disabled. Blame English for being interpretive.. :) – Nebulosar May 05 '21 at 14:46
  • My issue was that the form appear invalid, but when i tried to find an invalid form control, none of them was. The "fix" was to set some field as not disabled, but I don't know if it is appliable to every use case. – novembre May 05 '21 at 15:48
0

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

Ravinder Payal
  • 2,884
  • 31
  • 40
0

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

Sushil
  • 670
  • 7
  • 14
0

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);
Matko Milić
  • 11
  • 2
  • 2
0

Worked for FormGroup and FormArray. This method returns invalid control names.

private getInvalidControlNames(input: FormGroup | FormArray): string[] {
  let invalidControlNames: string[] = [];
  Object.keys(input.controls).forEach((controlName) => {
  const control = input.get(controlName)!;
  if (control.invalid && control instanceof FormControl) {
    invalidControlNames.push(controlName);
  } else if (
    control.invalid &&
    (control instanceof FormGroup || control instanceof FormArray)
  ) {
    invalidControlNames.push(...this.getInvalidControlNames(control));
  }
  });
  return [...new Set(invalidControlNames)];
}
  • I tried this one and it does seem to work. but the form.valid still returns false. might be from some external component embedded into the form – chitgoks Aug 04 '22 at 05:11
0

In my case, I found out that it was because I called markAsPristine() without manually refreshing the form validity... So the form was in an invalid state.

Calling formGroup.updateValueAndValidity() fixed the issue for me.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 10 '23 at 13:43
0

If you have an invalid form group, but all controls inside are valid, the controls might have been added to more than one form group.

Tom
  • 1,470
  • 15
  • 13
-1

Check empty or null form control value in html page

Form controls value: {{formname.value | json}}
Uttam Kar
  • 173
  • 1
  • 10