Angular 6 Reactive Forms : How to set focus on first invalid input
My Answer is inspired from yurzui's answer here. I'm using the logic from his answer to get the nativeElement
of a particular FormControl
by using it's FormControl
.
This is the logic that does that:
const originFormControlNameNgOnChanges = FormControlName.prototype.ngOnChanges;
FormControlName.prototype.ngOnChanges = function () {
const result = originFormControlNameNgOnChanges.apply(this, arguments);
this.control.nativeElement = this.valueAccessor._elementRef.nativeElement;
return result;
};
Now, the form's errors field would be null even though it's fields are invalid. So to get the exact first field that's invalid, we'll have to loop through all the fields and check for validity for each of them. I can write this logic in the onSubmitForm()
method. Something like this:
onSubmitForm() {
const fieldsToCheck = [
'codeBasicat',
'libellePef',
'nomApplication'
];
for (let i = 0; i < fieldsToCheck.length; i++) {
const fieldName = fieldsToCheck[i];
if (this.addItemfForm.get(fieldName).invalid) {
( < any > this.addItemfForm.get(fieldName)).nativeElement.focus();
break;
}
}
}
I've deliberately used for
instead of Array.forEach
as I wanted to break from the loop.
Hopefully this should do the trick for you.
Here's a Working Sample StackBlitz for your ref.
Use below code in your submit.
for (const key of Object.keys(this.addressForm.controls)) {
if (this.addressForm.controls[key].invalid) {
const invalidControl = this.el.nativeElement.querySelector('[formcontrolname="' + key + '"]');
invalidControl.focus();
break;
}
}
this.addressForm will be your FormGroup.
We don't even need directive here.
We can set focus on first invalid input simply by just write this code in the submit() of the form.
if(this.form.invalid)
{
// Got focus to the error field
let invalidFields = [].slice.call(document.getElementsByClassName('ng-invalid'));
invalidFields[1].focus();
}
I did that using directives. So My form would look like this:
<form [formGroup]="userForm" (submit)="saveData()" appFocus >
...
</form>
and the code for the directive itself:
import { Directive, HostListener, Input, ElementRef } from '@angular/core';
import { NgForm } from '@angular/forms';
@Directive({
selector: '[appFocus]'
})
export class FocusDirective {
constructor(private el: ElementRef) { }
@Input() formGroup: NgForm;
@HostListener('submit', ['$event'])
public onSubmit(event): void {
if ('INVALID' === this.formGroup.status) {
event.preventDefault();
const formGroupInvalid = this.el.nativeElement.querySelectorAll('.ng-invalid');
(<HTMLInputElement>formGroupInvalid[0]).focus();
}
}
}
However this solution is incomplete as there is a lot of corner cases that have to be considered. For example what if the first element is radio button group. Dispatching focus event will automatically mark the filed. Second not every element to which angular ads ng-invalid will be an input.