How to find the invalid controls in Angular(v2 onwards) reactive form
AngularAngular Reactive-FormsReactiveAngular 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:
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