How to set focus on input field?
Solution 1
- When a Modal is opened, set focus on a predefined <input> inside this Modal.
Define a directive and have it $watch a property/trigger so it knows when to focus the element:
Name: <input type="text" focus-me="shouldBeOpen">
app.directive('focusMe', ['$timeout', '$parse', function ($timeout, $parse) {
return {
//scope: true, // optionally create a child scope
link: function (scope, element, attrs) {
var model = $parse(attrs.focusMe);
scope.$watch(model, function (value) {
console.log('value=', value);
if (value === true) {
$timeout(function () {
element[0].focus();
});
}
});
// to address @blesh's comment, set attribute value to 'false'
// on blur event:
element.bind('blur', function () {
console.log('blur');
scope.$apply(model.assign(scope, false));
});
}
};
}]);
The $timeout seems to be needed to give the modal time to render.
'2.' Everytime <input> becomes visible (e.g. by clicking some button), set focus on it.
Create a directive essentially like the one above. Watch some scope property, and when it becomes true (set it in your ng-click handler), execute element[0].focus()
. Depending on your use case, you may or may not need a $timeout for this one:
<button class="btn" ng-click="showForm=true; focusInput=true">show form and
focus input</button>
<div ng-show="showForm">
<input type="text" ng-model="myInput" focus-me="focusInput"> {{ myInput }}
<button class="btn" ng-click="showForm=false">hide form</button>
</div>
app.directive('focusMe', function($timeout) {
return {
link: function(scope, element, attrs) {
scope.$watch(attrs.focusMe, function(value) {
if(value === true) {
console.log('value=',value);
//$timeout(function() {
element[0].focus();
scope[attrs.focusMe] = false;
//});
}
});
}
};
});
Update 7/2013: I've seen a few people use my original isolate scope directives and then have problems with embedded input fields (i.e., an input field in the modal). A directive with no new scope (or possibly a new child scope) should alleviate some of the pain. So above I updated the answer to not use isolate scopes. Below is the original answer:
Original answer for 1., using an isolate scope:
Name: <input type="text" focus-me="{{shouldBeOpen}}">
app.directive('focusMe', function($timeout) {
return {
scope: { trigger: '@focusMe' },
link: function(scope, element) {
scope.$watch('trigger', function(value) {
if(value === "true") {
$timeout(function() {
element[0].focus();
});
}
});
}
};
});
Original answer for 2., using an isolate scope:
<button class="btn" ng-click="showForm=true; focusInput=true">show form and
focus input</button>
<div ng-show="showForm">
<input type="text" focus-me="focusInput">
<button class="btn" ng-click="showForm=false">hide form</button>
</div>
app.directive('focusMe', function($timeout) {
return {
scope: { trigger: '=focusMe' },
link: function(scope, element) {
scope.$watch('trigger', function(value) {
if(value === true) {
//console.log('trigger',value);
//$timeout(function() {
element[0].focus();
scope.trigger = false;
//});
}
});
}
};
});
Since we need to reset the trigger/focusInput property in the directive, '=' is used for two-way databinding. In the first directive, '@' was sufficient. Also note that when using '@' we compare the trigger value to "true" since @ always results in a string.
Solution 2
##(EDIT: I've added an updated solution below this explanation)
Mark Rajcok is the man... and his answer is a valid answer, but it has had a defect (sorry Mark)...
...Try using the boolean to focus on the input, then blur the input, then try using it to focus the input again. It won't work unless you reset the boolean to false, then $digest, then reset it back to true. Even if you use a string comparison in your expression, you'll be forced to change the string to something else, $digest, then change it back. (This has been addressed with the blur event handler.)
So I propose this alternate solution:
Use an event, the forgotten feature of Angular.
JavaScript loves events after all. Events are inherently loosely coupled, and even better, you avoid adding another $watch to your $digest.
app.directive('focusOn', function() {
return function(scope, elem, attr) {
scope.$on(attr.focusOn, function(e) {
elem[0].focus();
});
};
});
So now you could use it like this:
<input type="text" focus-on="newItemAdded" />
and then anywhere in your app...
$scope.addNewItem = function () {
/* stuff here to add a new item... */
$scope.$broadcast('newItemAdded');
};
This is awesome because you can do all sorts of things with something like this. For one, you could tie into events that already exist. For another thing you start doing something smart by having different parts of your app publish events that other parts of your app can subscribe to.
Anyhow, this type of thing screams "event driven" to me. I think as Angular developers we try really hard to hammer $scope shaped pegs into event shape holes.
Is it the best solution? I don't know. It is a solution.
Updated Solution
After @ShimonRachlenko's comment below, I've changed my method of doing this slightly. Now I use a combination of a service and a directive that handles an event "behind the scenes":
Other than that, it's the same principal outlined above.
###Usage
<input type="text" focus-on="focusMe"/>
app.controller('MyCtrl', function($scope, focus) {
focus('focusMe');
});
###Source
app.directive('focusOn', function() {
return function(scope, elem, attr) {
scope.$on('focusOn', function(e, name) {
if(name === attr.focusOn) {
elem[0].focus();
}
});
};
});
app.factory('focus', function ($rootScope, $timeout) {
return function(name) {
$timeout(function (){
$rootScope.$broadcast('focusOn', name);
});
}
});
Solution 3
I have found some of the other answers to be overly complicated when all you really need is this
app.directive('autoFocus', function($timeout) {
return {
restrict: 'AC',
link: function(_scope, _element) {
$timeout(function(){
_element[0].focus();
}, 0);
}
};
});
usage is
<input name="theInput" auto-focus>
We use the timeout to let things in the dom render, even though it is zero, it at least waits for that - that way this works in modals and whatnot too
Solution 4
HTML has an attribute autofocus
.
<input type="text" name="fname" autofocus>
http://www.w3schools.com/tags/att_input_autofocus.asp
Solution 5
You can also use the jqlite functionality built into angular.
angular.element('.selector').trigger('focus');
Misha Moroshko
I build products that make humans happier. Previously Front End engineer at Facebook. Now, reimagining live experiences at https://muso.live
Updated on February 20, 2021Comments
-
Misha Moroshko over 3 years
What is the 'Angular way' to set focus on input field in AngularJS?
More specific requirements:
- When a Modal is opened, set focus on a predefined
<input>
inside this Modal. - Everytime
<input>
becomes visible (e.g. by clicking some button), set focus on it.
I tried to achieve the first requirement with
autofocus
, but this works only when the Modal is opened for the first time, and only in certain browsers (e.g. in Firefox it doesn't work).Any help will be appreciated.
- When a Modal is opened, set focus on a predefined
-
Misha Moroshko over 11 yearsThanks for your answer! Thought this might work in this specific case, I'm looking for a more generic solution to this problem (setting focus). This means that the directive shouldn't include any hard coded names (like
shouldBeOpen
). -
Mark Rajcok over 11 years@Misha, okay, I edited my answer made the directive more generic, with no hard-coded names, and an isolate scope.
-
Mark Rajcok over 11 yearsSee also @Josh's "focus" directive: stackoverflow.com/a/14859639/215945 He did not use an isolate scope in his implementation.
-
Sunil D. about 11 years@MarkRajcok just curious about this: this version works but if I set an
ng-model
on the input field, the model value is lost when I use this directive w/the isolate scope is used. The problem doesn't happen if I try Josh's version w/out the isolate scope. Still a newbie, and I'd like to understand the difference. Here is a Plunker that shows it. -
Mark Rajcok about 11 years@SunilD., ng-model and isolate scopes do not work together, see stackoverflow.com/questions/11896732/… If you want to use ng-model (and normally you do), I suggest using Josh's version. I didn't use ng-model in my answer only because I started with Misha's example, which didn't use it.
-
Andrew Brown almost 11 yearsI found that #1 works very well with AngularJS 1.0.6. However, when running under a debugger, I noticed that every time I dismissed and reopened my modal, I was seeing one more additional call to the function that sets focus than the time before. I modified that function slightly to unbind the watch when
value != "true"
, and that appeared to address my issue. -
Ben Lesh almost 11 yearsSo... on a stateful page I had the same thing going because $watch is $watch and angular developers love $watch... well I hit a problem, Mr @MarkRajcok, a problem I solved with the (basic) solution I proposed below.
-
Mark Rajcok almost 11 years@blesh, thanks. I hadn't noticed this issue since I only tested the modal case. I updated the first plunker and the directive code snippet with a more general solution: the directive now catches the blur event and sets the boolean to
false
using $parse. -
Ben Lesh almost 11 years@MarkRajcok - I'm actually about 90% of the way through development on a pull request for some outstanding issues regarding focus management in Angular that covers things like setting focus. In my proposed pull both the watch method and the event method are implemented. They both have advantages and disadvantages after all. It's Issue 2012 I linked a mailing list discussion to my proposed change in there too.
-
Edwin Dalorzo almost 11 years+1 for the roadmap reference. In fact I just saw it in the documentation of 1.2 still considered unstable (docs.angularjs.org/api/ng.directive:ngFocus)
-
Shimon Rachlenko almost 11 yearsYou need to wrap the call to
$broadcast
with$timeout
if you want this to work on entering the controller. Otherwise nice solution. -
Ben Lesh almost 11 years@ShimonRachlenko - Thanks. But I'm not sure what you mean by the $timeout. If I wanted to broadcast when the controller constructor was being processed, I'd just broadcast right then. A timeout wouldn't do anything but defer the broadcast to a later execution in the event loop.
-
Shimon Rachlenko almost 11 yearsYes, and that's enough for the directive to initialize. Overwise the event is broadcasted before the directive starts to listen to it.. Again, this is only needed when you want to trigger your directive when you enter the page.
-
Ben Lesh almost 11 yearsYou're correct. I guess I hadn't used it to focus on load. I'll update the answer with something more robust.
-
teleclimber over 10 years@EdwinDalorzo
ngFocus
appears to be a way to handlefocus
events, not a way to set the focus on an element. -
Venning over 10 yearsNote that this breaks ng-model on the <input> for some reason.
-
Gorm Casper over 10 yearsI used
focus-me="myForm.myInput.$error.required"
. Seems to work great! -
chovy over 10 yearsi use jQuery
$('#inputId').focus()
-- i know that's not angular, but c'mon. -
ecancil over 10 yearsThere would be several ways to do this, one possible way that is really straight forward and easy would be to on the scope(controller here) set the ID of the item that you want to focus to when you click the button, then in the directive simply listen to this. In this case you wouldn't have to place the directive anywhere in particular, just somewhere within that page (i've used these kind of watcher directives with success in the past, particularly with focusing things and scrolling things) - Then if you're using jquery (would make this simpler) just find that element id and focus it
-
Eugene Fidelin about 10 yearsSolution with zero timeout doesn't work for me if input is located in popup modal. But even 10 ms fix the issue
-
Dan Benamy about 10 yearsWithout jquery loaded: angular.forEach(document.querySelectorAll('.selector'), function(elem) { elem.focus(); });
-
Randolpho about 10 yearsThis is, by far, the most elegant, "angular way" solution. Even though I mostly just copied the code when I first ran into this problem, I'm glad you made a module for it! I honestly think it might be worth attempting to get it into the core angular.
-
VitalyB about 10 yearsIsn't it a bad practice to put that into a controller?
-
masimplo almost 10 yearsActually I believe {{ isVisible }} is creating a watch anyway, so the "No use of $watch" statement is incorrect.
-
Edhowler almost 10 yearsYes, I think you are right. But it still serves as an easy way to do it.
-
IanB almost 10 yearsWorks, but I'm seeing error
model.assign is not a function
(Firefox) andundefined is not a function
(Chrome). -
Jan Peša almost 10 yearsThis is not an 'angular way' and don't advise people to touch DOM in their controllers.
-
sports almost 10 yearsIf putting this jqlite line inside a controller is bad practice, wouldn't it be better to put this jqlite line inside a directive?
-
Sanjeev Singh almost 10 yearsWhy you need to us trigger? Its not good to use trigger even in jquery.
-
Dev93 over 9 years@ecancil: I like your approach because it's simplest, but you need to set the timeout to ~500ms in IE because the animation of the modal appearance results in a blinking cursor outside of the input. I don't know of a nice way to have this happen when animation ends, so I brute force it with the 500ms.
-
Agat over 9 yearsI am not sure that's a great approach for ViewModel-apps. In Angular it must be done via directives.
-
Ali Adravi over 9 yearswill not work, if you have to pull more data from database, it need to wait for the controller complete the pulling data, so add enough time in place of 0
-
Vel Murugan S over 9 yearsThe one and only answer, which I (a complete beginner) could understand. Instead of "afterLoad(notifyControllerEvent);", I used to "notifyControllerEvent()". Otherwise, it ended with some errors.
-
mmacneil007 over 9 years@IanB - place
if(model.assign!==undefined)
abovescope.$apply(model.assign(scope, false));
. You're probably not binding to an object on your scope and instead doing focusInput=true. -
hussainb over 9 yearsHad to wrap the element[0].focus() in a $timeout to make it work for me.
-
gx14 about 9 yearsThis approach somehow seems to have broken (only) on iOS 8.3. Anyone else seeing the same? Really can't figure out why - not getting any errors from the code executed in the directive. Same code works perfect on iOS 8.2, Chrome and Android 5.1.
-
Sanjeev Singh about 9 yearse.g.If someone changes a texbox A and you need to show pop up and set focus on other textbox B.
-
lk_vc about 9 yearsGood! However in my case,
$timeout
with50ms
is in need instead of0
. -
frst almost 9 yearsYou just recreated what html "label" does, you could just replace "div focus-input" with "label" and get rid of directive.
-
JVG almost 9 yearsI'm getting this error when using this:
Looking up elements via selectors is not supported by jqLite!
-
JVG almost 9 yearsAs far as I can tell you'll need to load jQuery as a dependancy first, in which case
angular.element
becomes a wrapper for$() / jQuery()
. So without that this won't work, and you're basically just using jQuery anyway (but correct me if I'm wrong) -
Sanjeev Singh almost 9 years@Jascination: jqLite is developed to removed jQuery dependency. You don't need whole jQuery to access an element in DOM. But if you have jQuery installed, then Angular will refer jQuery. Check this: docs.angularjs.org/api/angular.element
-
snowindy over 8 years@bbodenmiller My modal has a fade-out thing applied, when element gets constructed it is invisible (100% transparent), so browser blocks focus invocation silently. Apparently some milliseconds passed allow to focus on almost transparent but visible input/button.
-
Tom Mettam over 8 years@IanB The blur event 'fix' fails (model.assign is not a function) if attrs.focusMe is an expression rather than just a variable name. For example, it works with: focus-me="pageActive" but fails with: focus-me="pageActive==true"
-
A.W. over 8 yearsThe last example code is working for me in chrome on desktop setting focus to a textarea. I tried this on Iphone 5 in Safari and Chrome but the textarea is not focused. I tried also with timeout uncommented. Any ideas?
-
deltree over 8 yearsI found that occasionally, the inputs would show as
undefined
even after the$timeout
and was able to solve it by checkingif (value === true || value === undefined)
which seems to work well inng-repeat
s -
tekHedd over 8 yearsI am now using this directive and it works great, solves the general problem, and isn't overly complex. Gave up looking for a $timeout-free solution. Keep finding comments saying focus is "on the roadmap" for Angular 1.1 and 1.2, but I'm using 2.x and still no focus management. Heh.
-
JVG over 8 yearsFor those like @Ade who want to use this with a simple
ng-click
: Let's say clicking a button hasng-click="showInput = !showInput
on your input. Then, on your actual input, add inng-if="showInput"
. Toggling the button will make the directive re-run each time. I was having an issue with this as I was usingng-show
which is the wrong approach. -
Paul Speranza over 8 yearsThis worked for me with a 500ms delay as Jezz suggested. I'm using it with a bootbox/bootstrap modal.
-
Nocturno over 8 yearsUnfortunately, this only works once if at all. I used this on an Angular edit in place situation, and it worked great the first time in Chrome, not at all in Firefox. In Chrome, when I click another item to edit in place, or even on the same item again, it no longer gains focus. The docs state it works on page load, which only happens once in an Angular app. If only it were this simple, we'd all be sitting on a beach earning 20%!
-
Sohail Faruqui almost 8 years@Mark Rajcok: Thank you Mark for you explanation, I have a very basic question, why do we need timeout. in my case I need to set like 500 ms to make it work if I make it lesser (say 400) first time my view(dialog) open it will focus when i close and reopen it wont focus. but for your example timeout with 0 sec is enough. (sorry if its so basic question)
-
Mark Rajcok almost 8 years@SohailFaruqui, it is hard to guess, but I'll guess anyway.. you probably have a lot more going on in your app, and maybe there are multiple messages that get put into the message queue after the timeout-0 callback, but those messages are needed to completely render your dialog. Increasing the timeout puts the
focus()
into the message queue later, after those other messages have a chance to get into the queue. -
Adam Gerthel almost 8 yearsExample #2 doesn't work in the Plunker example (chrome, OSX desktop). The input isn't focused until pressed a second time.
-
Mark Rajcok almost 8 years@AdamGerthel, did you try uncommenting the
$timeout
? If so, and if that didn't work, you may need to increase the timeout value to a few hundred milliseconds:$timeout(function() { ... }, 500)
. -
jody tate almost 8 yearsper the docs, "link" functions are "postLink" by default. also see: bennadel.com/blog/…
-
Ryan M over 7 yearsyou will get an error running this unless jQuery is included, it should instead be element[0].focus()
-
Maria Ines Parnisari over 7 yearsI get this:
Looking up elements via selectors is not supported by jqLite!
-
Jonas Eriksson over 7 yearsYeah, @JordanC is likely including the full jQuery, as this is not available in jqLite.
-
Sebastianb over 7 years@VitalyB I had a case where i needed to blur an element after an ajax call. I could add just one line of pure JS in the callback inside the controller, or build an entire directive exclusively for the blur of that input. I just felt it was too much of a hassle, so I went for the first option.
-
Nick Whiu over 7 yearsElegant! I made minor changes - changing
if(name === attr.focusOn) {
toif(name == attr.focusOn) {
and called the factory $focus, so I can do$focus(item.id);
andfocus-on="{{ task.id }}"
-
Sonic Soul about 7 yearshmm for me the code works, as in i can see it executing properly and element[0] is the right control. however .focus() does not trigger a focus. possibly bootstrap or something else is interfering with this?
-
Alexander about 7 yearsPerfect for me. Thank you
-
Kunal almost 7 yearsThis should be the correct answer. It covers all use cases.
-
StefK almost 7 yearsI love this event-driven approach. However I had to use an extra $timeout in the directive-part, otherwise it wouldn't focus on the element, but on the body. My guess is this is because I'm using it on an element inside a uib-popover. The element is probably not visible yet the moment the event is received, so the $timeout is needed to wait for it.
-
Michael Plautz about 6 years@Stephane yes, so then it becomes
.directive('autofocus', ['$timeout', ($timeout) => ({link: (_, e) => $timeout(() => e[0].focus())})])
-
CodeMonkey almost 6 yearsThe is correct out of context, but not the AngularJS way of doing things
-
Mbengue Assane almost 6 yearsI had to wrap the
scope.$apply(model.assign(scope, false));
expression around a$timeout
to have it work with a typeahead field. Otherwise, item selection won't work. -
Jacob Stamm almost 5 yearsThis madness is why people can't seem to drop jQuery in their AngularJS projects. Adding dynamic
id
s and using$("#"+id).focus()
in event handlers may not be "the angular way", but it is so much simpler. -
Edgar Quintero over 4 yearsBest to not use vanilla in Angular, because Angular can't track it nor keep it in sync with everything else.
-
James almost 4 yearsYou still need the timeout() though.