Set element focus in angular way
Solution 1
The problem with your solution is that it does not work well when tied down to other directives that creates a new scope, e.g. ng-repeat
. A better solution would be to simply create a service function that enables you to focus elements imperatively within your controllers or to focus elements declaratively in the html.
JAVASCRIPT
Service
.factory('focus', function($timeout, $window) {
return function(id) {
// timeout makes sure that it is invoked after any other event has been triggered.
// e.g. click events that need to run before the focus or
// inputs elements that are in a disabled state but are enabled when those events
// are triggered.
$timeout(function() {
var element = $window.document.getElementById(id);
if(element)
element.focus();
});
};
});
Directive
.directive('eventFocus', function(focus) {
return function(scope, elem, attr) {
elem.on(attr.eventFocus, function() {
focus(attr.eventFocusId);
});
// Removes bound events in the element itself
// when the scope is destroyed
scope.$on('$destroy', function() {
elem.off(attr.eventFocus);
});
};
});
Controller
.controller('Ctrl', function($scope, focus) {
$scope.doSomething = function() {
// do something awesome
focus('email');
};
});
HTML
<input type="email" id="email" class="form-control">
<button event-focus="click" event-focus-id="email">Declarative Focus</button>
<button ng-click="doSomething()">Imperative Focus</button>
Solution 2
About this solution, we could just create a directive and attach it to the DOM element that has to get the focus when a given condition is satisfied. By following this approach we avoid coupling controller to DOM element ID's.
Sample code directive:
gbndirectives.directive('focusOnCondition', ['$timeout',
function ($timeout) {
var checkDirectivePrerequisites = function (attrs) {
if (!attrs.focusOnCondition && attrs.focusOnCondition != "") {
throw "FocusOnCondition missing attribute to evaluate";
}
}
return {
restrict: "A",
link: function (scope, element, attrs, ctrls) {
checkDirectivePrerequisites(attrs);
scope.$watch(attrs.focusOnCondition, function (currentValue, lastValue) {
if(currentValue == true) {
$timeout(function () {
element.focus();
});
}
});
}
};
}
]);
A possible usage
.controller('Ctrl', function($scope) {
$scope.myCondition = false;
// you can just add this to a radiobutton click value
// or just watch for a value to change...
$scope.doSomething = function(newMyConditionValue) {
// do something awesome
$scope.myCondition = newMyConditionValue;
};
});
HTML
<input focus-on-condition="myCondition">
Solution 3
I like to avoid DOM lookups, watches, and global emitters whenever possible, so I use a more direct approach. Use a directive to assign a simple function that focuses on the directive element. Then call that function wherever needed within the scope of the controller.
Here's a simplified approach for attaching it to scope. See the full snippet for handling controller-as syntax.
Directive:
app.directive('inputFocusFunction', function () {
'use strict';
return {
restrict: 'A',
link: function (scope, element, attr) {
scope[attr.inputFocusFunction] = function () {
element[0].focus();
};
}
};
});
and in html:
<input input-focus-function="focusOnSaveInput" ng-model="saveName">
<button ng-click="focusOnSaveInput()">Focus</button>
or in the controller:
$scope.focusOnSaveInput();
angular.module('app', [])
.directive('inputFocusFunction', function() {
'use strict';
return {
restrict: 'A',
link: function(scope, element, attr) {
// Parse the attribute to accomodate assignment to an object
var parseObj = attr.inputFocusFunction.split('.');
var attachTo = scope;
for (var i = 0; i < parseObj.length - 1; i++) {
attachTo = attachTo[parseObj[i]];
}
// assign it to a function that focuses on the decorated element
attachTo[parseObj[parseObj.length - 1]] = function() {
element[0].focus();
};
}
};
})
.controller('main', function() {});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<body ng-app="app" ng-controller="main as vm">
<input input-focus-function="vm.focusOnSaveInput" ng-model="saveName">
<button ng-click="vm.focusOnSaveInput()">Focus</button>
</body>
Edited to provide more explanation about the reason for this approach and to extend the code snippet for controller-as use.
Solution 4
You can try
angular.element('#<elementId>').focus();
for eg.
angular.element('#txtUserId').focus();
its working for me.
Solution 5
Another option would be to use Angular's built-in pub-sub architecture in order to notify your directive to focus. Similar to the other approaches, but it's then not directly tied to a property, and is instead listening in on it's scope for a particular key.
Directive:
angular.module("app").directive("focusOn", function($timeout) {
return {
restrict: "A",
link: function(scope, element, attrs) {
scope.$on(attrs.focusOn, function(e) {
$timeout((function() {
element[0].focus();
}), 10);
});
}
};
});
HTML:
<input type="text" name="text_input" ng-model="ctrl.model" focus-on="focusTextInput" />
Controller:
//Assume this is within your controller
//And you've hit the point where you want to focus the input:
$scope.$broadcast("focusTextInput");
Related videos on Youtube
Tiago
Updated on August 22, 2020Comments
-
Tiago over 3 years
After looking for examples of how set focus elements with angular, I saw that most of them use some variable to watch for then set focus, and most of them use one different variable for each field they want to set focus. In a form, with a lot of fields, that implies in a lot of different variables.
With jquery way in mind, but wanting to do that in angular way, I made a solution that we set focus in any function using the element's id, so, as I am very new in angular, I'd like to get some opinions if that way is right, have problems, whatever, anything that could help me do this the better way in angular.
Basically, I create a directive that watch a scope value defined by the user with directive, or the default's focusElement, and when that value is the same as the element's id, that element set focus itself.
angular.module('appnamehere') .directive('myFocus', function () { return { restrict: 'A', link: function postLink(scope, element, attrs) { if (attrs.myFocus == "") { attrs.myFocus = "focusElement"; } scope.$watch(attrs.myFocus, function(value) { if(value == attrs.id) { element[0].focus(); } }); element.on("blur", function() { scope[attrs.myFocus] = ""; scope.$apply(); }) } }; });
An input that needs to get focus by some reason, will do this way
<input my-focus id="input1" type="text" />
Here any element to set focus:
<a href="" ng-click="clickButton()" >Set focus</a>
And the example function that set focus:
$scope.clickButton = function() { $scope.focusElement = "input1"; }
Is that a good solution in angular? Does it have problems that with my poor experience I don't see yet?
-
user1821052 over 9 yearsI really like this solution. Can you explain, however, the reason for using $timeout a bit more? Is the reason you used it due to an "Angular Thing" or "DOM Thing"?
-
ryeballar over 9 yearsIt makes sure that it runs after any digest cycles that angular does, but this excludes digest cycles that are affected after an asynchronous action that is executed after the timeout.
-
user1821052 over 9 yearsThanks! For those wondering where this is referenced in the angular docs, here's the link (took me forever to find)
-
ryeballar over 9 yearswhat will happen when the
myCondition
$scope variable has been set to true already and then the user chooses to focus to another element, can you still retrigger the focus whenmyCondition
is already true, your code watches the changes for the attributefocusOnCondition
but it will not trigger when the value you try to change is still the same. -
Braulio over 9 yearsI'm going to update the sample, in our case we have two radio buttons, and we toggle the flag to true or false depending on the value, you could just change the myCondition flag to true or false
-
Pratik Gaikwad almost 9 years@ryeballar, Thanks!. Nice simple solution. Just a question though. Can I use the factory that is created via attribute instead of waiting for some event to happen?
-
ryeballar almost 9 years@PratikGaikwad can you provide a use case? You're question is quite vague. State an example.
-
Pratik Gaikwad almost 9 years@ryeballar, The html example you provided is like<button event-focus="click" event-focus-id="email">Declarative Focus</button>. What I am thinking is like this: <input type="email" id="email" class="form-control" focus> i.e adding it as an attribute to the textbox directly.
-
ryeballar almost 9 yearsThen how would you trigger a focus with that use case?, With the declarative html provided, it would have been a click event or any event that can be triggered from a button. But what would be trigger in the case you provided?
-
Gaurav Gandhi over 8 yearsOnly thing works is this ! Kudos. i wanted something i can use to focus on radio buttons, with hotkeys. This thing only worked. TY.
-
mortb about 8 yearsSeems like a generic solution. Better than depending on ids. I like it.
-
John Rix about 8 yearsNote: This would only work if using full jQuery rather than relying on jqLite embedded in Angular. See docs.angularjs.org/api/ng/function/angular.element
-
forgivenson about 8 yearsThis is the jQuery way of doing this, not an angular way. The question specifically asks for how to do it in an angular way.
-
Adrian Carr almost 8 yearsIn case anyone else tries this and it doesn't work, I had to change element.focus(); to element[0].focus();
-
setec almost 8 yearsThis solution is much more 'angular way' than id-based hack above.
-
Garret Wilson over 6 yearsThat's very nice, and has been working well for me. But now I have a set of inputs using
ng-repeat
, and I only want to set the focus function for the first one. Any idea how I could conditionally set a focus function for<input>
based on$index
for example? -
cstricklan over 6 yearsGlad it's useful. My angular 1 is a little rusty, but you should be able to add another attribute to the input, like
assign-focus-function-if="{{$index===0}}"
, and then as the first line of the directive exit early before assigning a function if that is not true:if (attr.assignFocusFunctionIf===false) return;
Note I'm checking if it's explicitlyfalse
and not just falsey so the directive will still work if that attribute isn't defined. -
Bruno Santos about 6 yearsIt's insane the amount of work that's necessary in angular just to focus an input.
-
Atomosk over 5 yearsController-as is much simplier with lodash.
_.set(scope, attributes.focusOnSaveInput, function() { element.focus(); })
. -
David Casillas over 5 yearsIs accessing the DOM in a Service the Angular way of doing things? Shouldn't that be restricted to directives?