Set focus to first invalid input in AngularJs form

I read several articles and StackOverflow questions regarding setting focus in AngularJs.

Unfortunately, all the examples that I read suggest that there is some attribute that I can add to the element to get focus, for example. a directive focusMe .

However, what if I do not know in advance which input to set the focus to? In particular, how to set focus on the first input element in a form with $ an invalid set, that is, an element that has not passed the test. There may be several inputs that do not validate the check, so I cannot use a directive that simply tries to call .focus () based on this. (I do this for reasons of accessibility / WCAG, its good practice to do this with a click of a button to minimize keystrokes to find the first field with a failed check).

The $ error object will provide all the controls that do not confirm the check, but they are grouped by type of failure not in any order of appearance on the form.

I'm sure I can come up with some kind of awkward way to do this. A directive in the form that receives some translation when you need to adjust focus is a directive that can then look for the first invalid element. However, this seems very complicated, and I would like to know if this is a more convenient angular way.

+60
angularjs validation angularjs-directive wcag
Dec 04 '13 at 1:28
source share
13 answers

So the answer was simpler than I thought.

All I needed was a directive to place the form itself, with an event handler that looked for a submit event. Then it can traverse the DOM, looking for the first element that has the .ng-invalid class on it.

An example of using jQLite:

myApp.directive('accessibleForm', function () { return { restrict: 'A', link: function (scope, elem) { // set up event handler on the form element elem.on('submit', function () { // find the first invalid element var firstInvalid = elem[0].querySelector('.ng-invalid'); // if we find one, set focus if (firstInvalid) { firstInvalid.focus(); } }); } }; }); 

The example here uses the Attribute directive, you can extend this example to be an element directive (limit: "E") and include a template that converts it to a. This, however, is a personal preference.

+87
Dec 05 :
source share

You can create a directive like some other answers or, alternatively, you can connect it using ng-submit and implement the logic in the controller.

View:

 <form name='yourForm' novalidate ng-submit="save(yourForm)"> </form> 

Controller:

 $scope.save = function(yourForm) { if (!yourForm.$valid) { angular.element("[name='" + yourForm.$name + "']").find('.ng-invalid:visible:first').focus(); return false; } }; 
+15
Oct 30 '15 at 2:05
source share

You can also use angular.element

 angular.element('input.ng-invalid').first().focus(); 

View

 <form name="myForm" novalidate="novalidate" data-ng-submit="myAction(myForm.$valid)" autocomplete="off"></form> 

controller

 $scope.myAction= function(isValid) { if (isValid) { //You can place your ajax call/http request here } else { angular.element('input.ng-invalid').first().focus(); } }; 

used by ngMessages to check

The way without jquery

 angular.element($document[0].querySelector('input.ng-invalid')).focus(); 

When using this method, you need to pass $document as a parameter in your angular controller

 angular.module('myModule') .controller('myController', ['$document', '$scope', function($document, $scope){ // Code Here }]); 
+13
Mar 16 '16 at 15:20
source share

You can use pure jQuery to select the first invalid input:

$('input.ng-invalid').first().focus();

+7
Apr 2 '15 at 5:28
source share

  .directive('accessibleForm', function () { return { restrict: 'A', link: function (scope, elem) { // set up event handler on the form element elem.on('submit', function () { // find the first invalid element var firstInvalid = elem[0].querySelector('.ng-invalid'); if (firstInvalid && firstInvalid.tagName.toLowerCase() === 'ng-form') { firstInvalid = firstInvalid.querySelector('.ng-invalid'); } // if we find one, set focus if (firstInvalid) { firstInvalid.focus(); } }); } }; }) 
+4
Oct 23 '15 at 10:43
source share

I played with this idea for a while, and I came up with my own solution, it can help people who negatively scan the DOM like me.

As far as I can tell, form elements are registered in sequential order (i.e. from top to bottom), and their names and validation states are available in scope through what has ever been in the form name (for example, $ scope.myForm).

This made me think that there was a way to find the first invalid form input without going around the DOM and instead scan the internal structures of angular js instead. Below is my solution, but it assumes that you have another way of focusing the form elements, I transmit according to the user directive if the translation matches the name of the element that it will concentrate on its own (which is useful in itself, since you get the control focuses on first boot).

Search function for the first invalid (ideally shared with controllers through the service)

 function findFirstInvalid(form){ for(var key in form){ if(key.indexOf("$") !== 0){ if(form[key].$invalid){ return key; } } } } 

And the custom focus directive

 directives.directive('focus', function($timeout){ return { require: 'ngModel', restrict: 'A', link: function(scope, elem, attrs, ctrl){ scope.$on('inputFocus', function(e, name){ if(attrs.name === name){ elem.focus(); } }); } } }); 
+2
Feb 20 '14 at 4:52
source share

I made some small changes to the great solution written by iandotkelly. This solution adds an animation that starts when scrolling, and then focuses on the selected item.

 myApp.directive('accessibleForm', function () { return { restrict: 'A', link: function (scope, elem) { // set up event handler on the form element elem.on('submit', function () { // find the first invalid element var firstInvalid = elem[0].querySelector('.ng-invalid'); // if we find one, we scroll with animation and then we set focus if (firstInvalid) { angular.element('html:not(:animated),body:not(:animated)') .animate({ scrollTop: angular.element(firstInvalid).parent().offset().top }, 350, 'easeOutCubic', function () { firstInvalid.focus(); }); } }); } }; }); 
+1
Oct. 26 '16 at 8:45
source share

only one line:

 if($scope.formName.$valid){ //submit } else{ $scope.formName.$error.required[0].$$element.focus(); } 
+1
Oct 27 '17 at 7:50
source share

You can add an attribute to each element of the form, which is a function (ideally a directive) that receives the field identifier. This field identifier should somehow correlate with your $ error object. The function can check if the identifier is in the $ error object, and if so, return the attribute parameter for the error.

 <input id="name" class="{{errorCheck('name')}}"> 

If you had a mistake, this would create it.

 <input id="name" class="error"> 

You can use this to define your style, and now you know which fields have errors. Unfortunately, you do not know what is the first field.

One solution would be to use jQuery and a .first filter. If you go this route, http://docs.angularjs.org/api/angular.element

Another solution would be to add a field order parameter for the function in your form fields: {{errorCheck ('name', 1)}}. You can specify the names of the error fields in an array, and then sort them by the field order parameter. This can give you more flexibility.

Hope this helps.

0
Dec 04 '13 at 2:13
source share

I was inspired by chaojidan above to offer this option for those using nested angular 1.5.9 ng-forms:

 class FormFocusOnErr implements ng.IDirective { static directiveId: string = 'formFocusOnErr'; restrict: string = "A"; link = (scope: ng.IScope, elem, attrs) => { // set up event handler on the form element elem.on('submit', function () { // find the first invalid element var firstInvalid = angular.element( elem[0].querySelector('.ng-invalid'))[0]; // if we find one, set focus if (firstInvalid) { firstInvalid.focus(); // ng-invalid appears on ng-forms as well as // the inputs that are responsible for the errors. // In such cases, the focus will probably fail // because we usually put the ng-focus attribute on divs // and divs don't support the focus method if (firstInvalid.tagName.toLowerCase() === 'ng-form' || firstInvalid.hasAttribute('ng-form') || firstInvalid.hasAttribute('data-ng-form')) { // Let try to put a finer point on it by selecting // the first visible input, select or textarea // that has the ng-invalid CSS class var firstVisibleInvalidFormInput = angular.element(firstInvalid.querySelector("input.ng-invalid,select.ng-invalid,textarea.ng-invalid")).filter(":visible")[0]; if (firstVisibleInvalidFormInput) { firstVisibleInvalidFormInput.focus(); } } } }); } } // Register in angular app app.directive(FormFocusOnErr.directiveId, () => new FormFocusOnErr()); 
0
Oct 24 '16 at 22:04
source share

This is because focus() not supported in jqLite and from Angular docs on an element.

0
Jun 27 '17 at 18:14
source share

A little tweaking of what @Sajan said worked for me,

 angular.element("[name='" + this.formName.$name + "']").find('.ng-invalid:visible:first')[0].focus(); 
0
Jun 25 '19 at 5:55
source share

A non-guidance method might look like this. This is what I used, since I have the “next” button at the bottom of each page, which is actually located in index.html in the footer. I use this code in main.js.

 if (!$scope.yourformname.$valid) { // find the invalid elements var visibleInvalids = angular.element.find('.ng-invalid:visible'); if (angular.isDefined(visibleInvalids)){ // if we find one, set focus visibleInvalids[0].focus(); } return; } 
-one
Jun 26 '15 at 15:25
source share



All Articles