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

20

189

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?

Cule answered 20/7, 2017 at 16:11 Comment(4)
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 listChaffer
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/10530Sheryl
When formGroup.errors gives null and formGroup.valid gives false, it might be because of html tags like maxlength etc. They should not be mixedParticulate
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.Shoreward
L
326

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;
    }
Logotype answered 20/7, 2017 at 16:14 Comment(13)
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 falseCule
what does findInvalidControls() return you?Logotype
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.Cule
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 causeCule
@junky, yeah, it can be it :). Pending means that async validation is in progressLogotype
I'm not sure it's a good idea but I changed the if line like this, if (controls[name].status != "VALID" {Cule
that's the internal implementation, no need to do that :)Logotype
@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 invalidCrackup
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 invalidHangnail
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!Nondescript
could write this in shorter using ES6 features. return controls.filter((control) => control.invalid).map((control) => control.name)Pleomorphism
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)); } }Recurvate
In the case that a field does have value that passes all validations but is still logged as invalid, be sure that you're not manually assigning value to formControl.value like so someControl.value = value or someControl.value.push(value) (common anti-pattern for multi-select fields). Instead, use someControl.setValue(value).Normative
O
80

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

Osmious answered 6/11, 2019 at 10:7 Comment(3)
This works really well thank youMacedoine
What if only form itself is returned with ng-invalid?Exertion
@SarenTasciyan A form can only be invalid if one of its controls is invalid, in which case the invalid control will also be shown. Once that is fixed the form will be valid again. Hope that answers your question.Osmious
A
63

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;
  }
Astra answered 13/9, 2018 at 11:12 Comment(2)
Oh wow. Thanks a lot that saved me a lot of investigation-time!Turkish
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.Metaprotein
I
23

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

this.AddCustomerForm.markAllAsTouched();
Inclinometer answered 16/4, 2020 at 21:1 Comment(1)
Going to give this a +1, as it helped me find out what I needed to know --- which is to show validation messages when the user hasn't necessarily touched the inputs.Danette
S
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

Southwesterly answered 10/5, 2020 at 13:39 Comment(0)
C
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
     })
Chaffer answered 20/7, 2017 at 16:18 Comment(5)
this returns null even there are empty controlsCule
It should return null when there are no errors. Can you post your template?Chaffer
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.Gervase
I can access errors for each individual contorls like this.form.controls['Email'].errorsSchaaf
@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 )Chaffer
B
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;
  }
Bushey answered 19/2, 2020 at 19:7 Comment(0)
N
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.

Nystatin answered 5/5, 2021 at 14:14 Comment(1)
You have fought well, gallant knightSuperfluity
G
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;
}
Gig answered 5/9, 2018 at 19:48 Comment(3)
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.Astra
I improved my answer, to fit your use case.Gig
you can use this to literate over the controls: Object.entries(input.controls).forEach(([, control]) => { switch (control.constructor.name) { ... } })Gaskin
S
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".

Sinclair answered 27/6, 2018 at 5:46 Comment(0)
E
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

Exposed answered 3/7, 2019 at 6:52 Comment(0)
S
1

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

Summarize answered 20/1, 2020 at 15:37 Comment(0)
T
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

Tymbal answered 23/4, 2021 at 12:18 Comment(2)
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.. :)Nystatin
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.Tymbal
S
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];
        }
     });
  }

Sketchy answered 14/4, 2021 at 21:10 Comment(0)
N
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!!!

Neal answered 18/5, 2022 at 12:34 Comment(0)
P
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);
Pedropedrotti answered 20/5, 2022 at 12:52 Comment(0)
E
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)];
}
Estheresthesia answered 28/6, 2022 at 6:18 Comment(1)
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 formTypewrite
M
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.

Mayes answered 8/2, 2023 at 1:50 Comment(1)
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.Valentinavalentine
C
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.

Calfskin answered 8/3, 2023 at 16:32 Comment(0)
B
-1

Check empty or null form control value in html page

Form controls value: {{formname.value | json}}
Butterscotch answered 12/12, 2020 at 6:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.