774

What is the 'Angular way' to set focus on input field in AngularJS?

More specific requirements:

  1. When a Modal is opened, set focus on a predefined <input> inside this Modal.
  2. Every time <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).

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746

33 Answers33

591
  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));
            });
        }
    };
}]);

Plunker

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;
          //});
        }
      });
    }
  };
});

Plunker


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(); 
          });
        }
      });
    }
  };
});

Plunker.

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;
          //});
        }
      });
    }
  };
});

Plunker.

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.

JsAndDotNet
  • 16,260
  • 18
  • 100
  • 123
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Thanks 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`). – Misha Moroshko Feb 13 '13 at 11:36
  • @Misha, okay, I edited my answer made the directive more generic, with no hard-coded names, and an isolate scope. – Mark Rajcok Feb 13 '13 at 16:20
  • 2
    See also @Josh's "focus" directive: http://stackoverflow.com/a/14859639/215945 He did not use an isolate scope in his implementation. – Mark Rajcok Feb 14 '13 at 16:45
  • 3
    @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](http://plnkr.co/edit/oK53wl?p=preview) that shows it. – Sunil D. Jun 20 '13 at 01:24
  • @SunilD., ng-model and isolate scopes do not work together, see http://stackoverflow.com/questions/11896732/ngmodel-and-component-with-isolated-scope 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. – Mark Rajcok Jun 20 '13 at 03:39
  • 2
    I 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. – Andrew Brown Jul 14 '13 at 16:25
  • 1
    So... 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. – Ben Lesh Aug 18 '13 at 03:32
  • 1
    @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. – Mark Rajcok Aug 20 '13 at 19:12
  • 1
    @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](https://github.com/angular/angular.js/issues/2012/) I linked a [mailing list discussion](https://groups.google.com/forum/?hl=en#!topic/angular/EQSWV1chf0A) to my proposed change in there too. – Ben Lesh Aug 20 '13 at 20:32
  • Note that this breaks ng-model on the for some reason. – Venning Nov 03 '13 at 00:50
  • I used `focus-me="myForm.myInput.$error.required"`. Seems to work great! – Benjamin Atkin Nov 26 '13 at 09:18
  • 1
    i use jQuery `$('#inputId').focus()` -- i know that's not angular, but c'mon. – chovy Dec 12 '13 at 07:24
  • 1
    Works, but I'm seeing error `model.assign is not a function` (Firefox) and `undefined is not a function` (Chrome). – IanB Sep 01 '14 at 23:30
  • @IanB - place `if(model.assign!==undefined)` above `scope.$apply(model.assign(scope, false));`. You're probably not binding to an object on your scope and instead doing focusInput=true. – mmacneil007 Feb 02 '15 at 21:51
  • This 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. – gx14 Apr 29 '15 at 19:15
  • 3
    @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" – Tom Mettam Oct 21 '15 at 13:08
  • The 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? – A.W. Oct 29 '15 at 07:23
  • I found that occasionally, the inputs would show as `undefined` even after the `$timeout` and was able to solve it by checking `if (value === true || value === undefined)` which seems to work well in `ng-repeat`s – deltree Nov 18 '15 at 03:02
  • @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) – Sohail Faruqui Aug 24 '16 at 13:56
  • @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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop) 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. – Mark Rajcok Aug 24 '16 at 15:56
  • Example #2 doesn't work in the Plunker example (chrome, OSX desktop). The input isn't focused until pressed a second time. – Adam Gerthel Aug 25 '16 at 16:39
  • @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)`. – Mark Rajcok Aug 25 '16 at 16:43
  • 4
    hmm 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? – Sonic Soul Apr 25 '17 at 15:58
  • 1
    I 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. – Mbengue Assane Aug 13 '18 at 22:53
  • This 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. – Jacob Stamm Aug 07 '19 at 18:38
269

##(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.

Here is a quick demo Plunk

###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);
    });
  }
});
peterh
  • 11,875
  • 18
  • 85
  • 108
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • 3
    You need to wrap the call to `$broadcast` with `$timeout` if you want this to work on entering the controller. Otherwise nice solution. – Shimon Rachlenko Sep 10 '13 at 08:35
  • @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. – Ben Lesh Sep 10 '13 at 12:43
  • 1
    Yes, 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. – Shimon Rachlenko Sep 11 '13 at 10:58
  • 2
    You're correct. I guess I hadn't used it to focus on load. I'll update the answer with something more robust. – Ben Lesh Sep 11 '13 at 13:34
  • 1
    This 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. – Randolpho Jun 12 '14 at 21:46
  • Elegant! I made minor changes - changing `if(name === attr.focusOn) {` to `if(name == attr.focusOn) {` and called the factory $focus, so I can do `$focus(item.id);` and `focus-on="{{ task.id }}"` – Nick Whiu Mar 07 '17 at 00:53
  • I 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. – StefK Sep 12 '17 at 12:13
244

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

SHernandez
  • 1,060
  • 1
  • 14
  • 21
ecancil
  • 2,461
  • 1
  • 13
  • 5
  • 1
    There 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 – ecancil Mar 01 '14 at 05:30
  • 14
    Solution with zero timeout doesn't work for me if input is located in popup modal. But even 10 ms fix the issue – Eugene Fidelin Apr 11 '14 at 16:05
  • @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. – Dev93 Oct 10 '14 at 23:55
  • will 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 – Ali Adravi Nov 20 '14 at 19:20
  • 2
    For those like @Ade who want to use this with a simple `ng-click`: Let's say clicking a button has `ng-click="showInput = !showInput` on your input. Then, on your actual input, add in `ng-if="showInput"`. Toggling the button will make the directive re-run each time. I was having an issue with this as I was using `ng-show` which is the wrong approach. – JVG Feb 29 '16 at 06:11
  • This worked for me with a 500ms delay as Jezz suggested. I'm using it with a bootbox/bootstrap modal. – Paul Speranza Mar 10 '16 at 14:25
94

HTML has an attribute autofocus.

<input type="text" name="fname" autofocus>

http://www.w3schools.com/tags/att_input_autofocus.asp

Rayron Victor
  • 2,398
  • 1
  • 25
  • 25
  • 33
    Unfortunately, this only works once if at all. I used this on an Angular [edit in place](http://stackoverflow.com/a/15453512/1683718) 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%! – Nocturno Mar 20 '16 at 16:22
63

You can also use the jqlite functionality built into angular.

angular.element('.selector').trigger('focus');

JordanC
  • 4,339
  • 1
  • 22
  • 16
  • 2
    Without jquery loaded: angular.forEach(document.querySelectorAll('.selector'), function(elem) { elem.focus(); }); – Dan Benamy Jun 05 '14 at 05:01
  • 29
    Isn't it a bad practice to put that into a controller? – VitalyB Jun 17 '14 at 09:17
  • 2
    If putting this jqlite line inside a controller is bad practice, wouldn't it be better to put this jqlite line inside a directive? – sports Sep 12 '14 at 19:02
  • Why you need to us trigger? Its not good to use trigger even in jquery. – Sanjeev Singh Sep 21 '14 at 23:07
  • 3
    I get this: `Looking up elements via selectors is not supported by jqLite!` – Maria Ines Parnisari Oct 24 '16 at 17:18
  • Yeah, @JordanC is likely including the full jQuery, as this is not available in jqLite. – Jonas Eriksson Nov 17 '16 at 15:46
  • 1
    @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. – Sebastianb Nov 17 '16 at 16:13
56

This works well and an angular way to focus input control

angular.element('#elementId').focus()

This is although not a pure angular way of doing the task yet the syntax follows angular style. Jquery plays role indirectly and directly access DOM using Angular (jQLite => JQuery Light).

If required, this code can easily be put inside a simple angular directive where element is directly accessible.

Sanjeev Singh
  • 3,976
  • 3
  • 33
  • 38
  • I am not sure that's a great approach for ViewModel-apps. In Angular it must be done via directives. – Agat Nov 06 '14 at 10:46
  • e.g.If someone changes a texbox A and you need to show pop up and set focus on other textbox B. – Sanjeev Singh Apr 30 '15 at 00:11
  • 1
    I'm getting this error when using this: `Looking up elements via selectors is not supported by jqLite!` – JVG Sep 27 '15 at 11:15
  • 6
    As 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) – JVG Sep 27 '15 at 11:21
  • @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: http://docs.angularjs.org/api/angular.element – Sanjeev Singh Sep 28 '15 at 08:57
30

I don't think $timeout is a good way to focus the element on creation. Here is a method using built-in angular functionality, dug out from the murky depths of the angular docs. Notice how the "link" attribute can be split into "pre" and "post", for pre-link and post-link functions.

Working Example: http://plnkr.co/edit/Fj59GB

// this is the directive you add to any element you want to highlight after creation
Guest.directive('autoFocus', function() {
    return {
        link: {
            pre: function preLink(scope, element, attr) {
                console.debug('prelink called');
                // this fails since the element hasn't rendered
                //element[0].focus();
            },
            post: function postLink(scope, element, attr) {
                console.debug('postlink called');
                // this succeeds since the element has been rendered
                element[0].focus();
            }
        }
    }
});
<input value="hello" />
<!-- this input automatically gets focus on creation -->
<input value="world" auto-focus />

Full AngularJS Directive Docs: https://docs.angularjs.org/api/ng/service/$compile

Cody Moniz
  • 4,845
  • 3
  • 22
  • 20
  • 2
    Had to wrap the element[0].focus() in a $timeout to make it work for me. – hussainb Apr 01 '15 at 17:10
  • @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. – snowindy Oct 08 '15 at 17:42
  • 2
    per the docs, "link" functions are "postLink" by default. also see: http://www.bennadel.com/blog/2746-the-post-link-function-is-the-link-function-in-angularjs-directives.htm – jody tate Aug 31 '16 at 15:24
18

Here is my original solution:

plunker

var app = angular.module('plunker', []);
app.directive('autoFocus', function($timeout) {
    return {
        link: function (scope, element, attrs) {
            attrs.$observe("autoFocus", function(newValue){
                if (newValue === "true")
                    $timeout(function(){element[0].focus()});
            });
        }
    };
});

And the HTML:

<button ng-click="isVisible = !isVisible">Toggle input</button>
<input ng-show="isVisible" auto-focus="{{ isVisible }}" value="auto-focus on" />

What it does:

It focuses the input as it becomes visible with ng-show. No use of $watch or $on here.

SSteve
  • 10,550
  • 5
  • 46
  • 72
Edhowler
  • 715
  • 8
  • 17
17

I've written a two-way binding focus directive, just like model recently.

You can use the focus directive like this:

<input focus="someFocusVariable">

If you make someFocusVariable scope variable true in anywhere in your controller, the input get focused. And if you want to "blur" your input then, someFocusVariable can be set to false. It's like Mark Rajcok's first answer but with two-way binding.

Here is the directive:

function Ctrl($scope) {
  $scope.model = "ahaha"
  $scope.someFocusVariable = true; // If you want to focus initially, set this to true. Else you don't need to define this at all.
}

angular.module('experiement', [])
  .directive('focus', function($timeout, $parse) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
          scope.$watch(attrs.focus, function(newValue, oldValue) {
              if (newValue) { element[0].focus(); }
          });
          element.bind("blur", function(e) {
              $timeout(function() {
                  scope.$apply(attrs.focus + "=false"); 
              }, 0);
          });
          element.bind("focus", function(e) {
              $timeout(function() {
                  scope.$apply(attrs.focus + "=true");
              }, 0);
          })
      }
    }
  });

Usage:

<div ng-app="experiement">
  <div ng-controller="Ctrl">
    An Input: <input ng-model="model" focus="someFocusVariable">
    <hr>
        <div ng-click="someFocusVariable=true">Focus!</div>  
        <pre>someFocusVariable: {{ someFocusVariable }}</pre>
        <pre>content: {{ model }}</pre>
  </div>
</div>

Here is the fiddle:

http://fiddle.jshell.net/ubenzer/9FSL4/8/

Pramesh Bajracharya
  • 2,153
  • 3
  • 28
  • 54
Umut Benzer
  • 3,476
  • 4
  • 36
  • 53
11

For those who use Angular with the Bootstrap plugin:

http://angular-ui.github.io/bootstrap/#/modal

You can hook into the opened promise of the modal instance:

modalInstance.opened.then(function() {
        $timeout(function() {
            angular.element('#title_input').trigger('focus');
        });
    });

modalInstance.result.then(function ( etc...
BPH
  • 527
  • 6
  • 10
8

I found it useful to use a general expression. This way you can do stuff like automatically move focus when input text is valid

<button type="button" moo-focus-expression="form.phone.$valid">

Or automatically focus when the user completes a fixed length field

<button type="submit" moo-focus-expression="smsconfirm.length == 6">

And of course focus after load

<input type="text" moo-focus-expression="true">

The code for the directive:

.directive('mooFocusExpression', function ($timeout) {
    return {
        restrict: 'A',
        link: {
            post: function postLink(scope, element, attrs) {
                scope.$watch(attrs.mooFocusExpression, function (value) {

                    if (attrs.mooFocusExpression) {
                        if (scope.$eval(attrs.mooFocusExpression)) {
                            $timeout(function () {
                                element[0].focus();
                            }, 100); //need some delay to work with ng-disabled
                        }
                    }
                });
            }
        }
    };
});
winry
  • 311
  • 2
  • 10
7

Not to resurrect a zombie or plug my own directive (ok that's exactly what I'm doing):

https://github.com/hiebj/ng-focus-if

http://plnkr.co/edit/MJS3zRk079Mu72o5A9l6?p=preview

<input focus-if />

(function() {
    'use strict';
    angular
        .module('focus-if', [])
        .directive('focusIf', focusIf);

    function focusIf($timeout) {
        function link($scope, $element, $attrs) {
            var dom = $element[0];
            if ($attrs.focusIf) {
                $scope.$watch($attrs.focusIf, focus);
            } else {
                focus(true);
            }
            function focus(condition) {
                if (condition) {
                    $timeout(function() {
                        dom.focus();
                    }, $scope.$eval($attrs.focusDelay) || 0);
                }
            }
        }
        return {
            restrict: 'A',
            link: link
        };
    }
})();
Jon Hieb
  • 760
  • 8
  • 9
  • I 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. – tekHedd Jan 20 '16 at 17:40
6

First, an official way to do focus is on the roadmap for 1.1. Meanwhile, you can write a directive to implement setting focus.

Second, to set focus on an item after it has become visible currently requires a workaround. Just delay your call to element focus() with a $timeout.

Because the same controller-modifies-DOM problem exists for focus, blur and select, I propose having an ng-target directive:

<input type="text" x-ng-model="form.color" x-ng-target="form.colorTarget">
<button class="btn" x-ng-click="form.colorTarget.focus()">do focus</button>

Angular thread here: http://goo.gl/ipsx4 , and more details blogged here: http://goo.gl/4rdZa

The following directive will create a .focus() function inside your controller as specified by your ng-target attribute. (It creates a .blur() and a .select() too.) Demo: http://jsfiddle.net/bseib/WUcQX/

broc.seib
  • 21,643
  • 8
  • 63
  • 62
  • +1 for the roadmap reference. In fact I just saw it in the documentation of 1.2 still considered unstable (http://docs.angularjs.org/api/ng.directive:ngFocus) – Edwin Dalorzo Aug 25 '13 at 00:21
  • 28
    @EdwinDalorzo `ngFocus` appears to be a way to handle `focus` events, not a way to **set** the focus on an element. – teleclimber Oct 26 '13 at 23:23
6

Instead of creating your own directive, it's possible to simply use javascript functions to accomplish a focus.

Here is an example.

In the html file:

<input type="text" id="myInputId" />

In a file javascript, in a controller for example, where you want to activate the focus:

document.getElementById("myInputId").focus();
user3657103
  • 207
  • 3
  • 10
5

If you just wanted a simple focus that was controlled by an ng-click.

Html:

<input ut-focus="focusTigger">

<button ng-click="focusTrigger=!focusTrigger" ng-init="focusTrigger=false"></button>

Directive:

'use strict'

angular.module('focus',['ng'])
.directive('utFocus',function($timeout){
    return {
        link:function(scope,elem,attr){
            var focusTarget = attr['utFocus'];
            scope.$watch(focusTarget,function(value){
                $timeout(function(){
                    elem[0].focus();
                });
            });
        }
    }
});
TOBlender
  • 1,053
  • 11
  • 17
4

A simple one that works well with modals:

.directive('focusMeNow', ['$timeout', function ($timeout)
{
    return {
        restrict: 'A',

        link: function (scope, element, attrs)
        {


            $timeout(function ()
            {
                element[0].focus();
            });



        }
    };
}])

Example

<input ng-model="your.value" focus-me-now />
Shawn Dotey
  • 616
  • 8
  • 11
3

You could just create a directive that forces focus on the decorated element on postLinking:

angular.module('directives')
.directive('autoFocus', function() {
    return {
        restrict: 'AC',
        link: function(_scope, _element) {
            _element[0].focus();
        }
    };
});

Then in your html:

<input type="text" name="first" auto-focus/> <!-- this will get the focus -->
<input type="text" name="second"/>

This would work for modals and ng-if toggled elements, not for ng-show since postLinking happens only on HTML processing.

ibaixas
  • 131
  • 1
  • 3
3

Mark and Blesh have great answers; however, Mark's has a flaw that Blesh points out (besides being complex to implement), and I feel that Blesh's answer has a semantic error in creating a service that's specifically about sending focus request to the frontend when really all he needed was a way to delay the event until all the directives were listening.

So here is what I ended up doing which steals a lot from Blesh's answer but keeps the semantics of the controller event and the "after load" service separate.

This allows the controller event to easily be hooked for things other than just focusing a specific element and also allows to incur the overhead of the "after load" functionality only if it is needed, which it may not be in many cases.

Usage

<input type="text" focus-on="controllerEvent"/>
app.controller('MyCtrl', function($scope, afterLoad) {
  function notifyControllerEvent() {
      $scope.$broadcast('controllerEvent');
  }

  afterLoad(notifyControllerEvent);
});

Source

app.directive('focusOn', function() {
   return function(scope, elem, attr) {
      scope.$on(attr.focusOn, function(e, name) {
        elem[0].focus();
      });
   };
});

app.factory('afterLoad', function ($rootScope, $timeout) {
  return function(func) {
    $timeout(func);
  }
});
joshperry
  • 41,167
  • 16
  • 88
  • 103
  • The 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. – Vel Murugan S Jan 07 '15 at 11:45
3

This is also possible to use ngModelController. Working with 1.6+ (don't know with older versions).

HTML

<form name="myForm">
    <input type="text" name="myText" ng-model="myText">
</form>

JS

$scope.myForm.myText.$$element.focus();

--

N.B.: Depending of the context, you maybe have to wrap in a timeout function.

N.B.²: When using controllerAs, this is almost the same. Just replace name="myForm" with name="vm.myForm" and in JS, vm.myForm.myText.$$element.focus();.

Ludovic Guillaume
  • 3,237
  • 1
  • 24
  • 41
3

Probably, the simplest solution on the ES6 age.

Adding following one liner directive makes HTML 'autofocus' attribute effective on Angular.js.

.directive('autofocus', ($timeout) => ({link: (_, e) => $timeout(() => e[0].focus())}))

Now, you can just use HTML5 autofocus syntax like:

<input type="text" autofocus>
Tsuneo Yoshioka
  • 7,504
  • 4
  • 36
  • 32
  • @Stephane yes, so then it becomes `.directive('autofocus', ['$timeout', ($timeout) => ({link: (_, e) => $timeout(() => e[0].focus())})])` – Michael Plautz Apr 25 '18 at 20:16
2

Just a newbie here, but I was abble to make it work in a ui.bootstrap.modal with this directive:

directives.directive('focus', function($timeout) {
    return {
        link : function(scope, element) {
            scope.$watch('idToFocus', function(value) {
                if (value === element[0].id) {
                    $timeout(function() {
                        element[0].focus();
                    });
                }
            });
        }
    };
});

and in the $modal.open method I used the folowing to indicate the element where the focus should be putted:

var d = $modal.open({
        controller : function($scope, $modalInstance) {
            ...
            $scope.idToFocus = "cancelaAteste";
    }
        ...
    });

on the template I have this:

<input id="myInputId" focus />
2

I edit Mark Rajcok's focusMe directive to work for multiple focus in one element.

HTML:

<input  focus-me="myInputFocus"  type="text">

in AngularJs Controller:

$scope.myInputFocus= true;

AngulaJS Directive:

app.directive('focusMe', function ($timeout, $parse) {
    return {
        link: function (scope, element, attrs) {
            var model = $parse(attrs.focusMe);
            scope.$watch(model, function (value) {
                if (value === true) {
                    $timeout(function () {
                        scope.$apply(model.assign(scope, false));
                        element[0].focus();
                    }, 30);
                }
            });
        }
    };
});
Mohamad Khani
  • 332
  • 4
  • 13
2

The following directive did the trick for me. Use the same autofocus html attribute for input.

.directive('autofocus', [function () {
    return {
        require : 'ngModel',
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.focus();
        }
    };
}])
Kishore Relangi
  • 1,928
  • 16
  • 18
  • 1
    you will get an error running this unless jQuery is included, it should instead be element[0].focus() – Ryan M Oct 06 '16 at 16:06
2

If you are using modalInstance and have the object you can use "then" to do actions after opening the modal. If you are not using the modalInstance, and hard coded to open the modal you can use the event. The $timeout is not a good solution.

You can do (Bootstrap3):

$("#" + modalId).on("shown.bs.modal", function() {
    angular.element("[name='name']").focus();
});

At modalInstance you can look at library to how execute the code after open modal.

Don't use $timeout like this, the $timeout can be 0, 1, 10, 30, 50, 200 or more this will depend on client computer, and the process to open modal.

Don't use $timeout let the method tell you when you can focus ;)

I hope that this help! :)

2

All of the previous answer doesn't work if the desired focus element is injected in a directive template. The following directive fit to both simple element or directive injected element (I wrote it in typescript). it accept selector for inner focusable element. if you just need to focus the self element - don't send any selector parameter to the directive :

module APP.Directives {

export class FocusOnLoadDirective implements ng.IDirective {
    priority = 0;
    restrict = 'A';

    constructor(private $interval:any, private $timeout:any) {

    }

    link = (scope:ng.IScope, element:JQuery, attrs:any) => {
        var _self = this;
        var intervalId:number = 0;


        var clearInterval = function () {
            if (intervalId != 0) {
                _self.$interval.cancel(intervalId);
                intervalId = 0;
            }
        };

        _self.$timeout(function(){

                intervalId = _self.$interval(function () {
                    let focusableElement = null;
                    if (attrs.focusOnLoad != '') {
                        focusableElement = element.find(attrs.focusOnLoad);
                    }
                    else {
                        focusableElement = element;
                    }
                    console.debug('focusOnLoad directive: trying to focus');
                    focusableElement.focus();
                    if (document.activeElement === focusableElement[0]) {
                        clearInterval();
                    }
                }, 100);

                scope.$on('$destroy', function () {
                    // Make sure that the interval is destroyed too
                    clearInterval();
                });

        });
    };

    public static factory = ():ng.IDirectiveFactory => {
        let directive = ($interval:any, $timeout:any) => new FocusOnLoadDirective($interval, $timeout);
        directive.$inject = ['$interval', '$timeout'];
        return directive;
    };
}

angular.module('common').directive('focusOnLoad', FocusOnLoadDirective.factory());

}

usage example for simple element:

<button tabindex="0" focus-on-load />

usage example for inner element (usually for dynamic injected element like directive with template):

<my-directive focus-on-load="input" />

you can use any jQuery selector instead of "input"

2

If you wish to set focus on particular element, you can use below approach.

  1. Create a service called focus.

    angular.module('application')
    .factory('focus', function ($timeout, $window) {
        return function (id) {
            $timeout(function () {
                var element = $window.document.getElementById(id);
                if (element)
                    element.focus();
            });
        };
    });
    
  2. Inject it into the controller from where you wish to call.

  3. Call this service.

Rakesh Burbure
  • 1,045
  • 12
  • 27
1

I want to contribute to this discussion after searching for at better solution and not finding it, having to create it instead.

Criteria: 1. Solution should be independent of parent controller scope to increase re-usability. 2. Avoid the use of $watch to monitor some condition, this is both slow, increases the size of the digest loop and makes testing harder. 3. Avoid $timeout or $scope.$apply() to trigger a digest loop. 4. An input element is present within the element where the Directive is used open.

This is the solution I liked the most:

Directive:

.directive('focusInput', [ function () {
    return {
        scope: {},
        restrict: 'A',
        compile: function(elem, attr) {
            elem.bind('click', function() {
                elem.find('input').focus();                
            });
        }        
    };
}]);

Html:

 <div focus-input>
     <input/>
 </div>

I hope this will help someone out there!

xiaoma
  • 11
  • 3
  • You just recreated what html "label" does, you could just replace "div focus-input" with "label" and get rid of directive. – frst Sep 14 '15 at 10:41
0

I think the directive is unnecessary. Use HTML id and class attributes to select the required element and have the service use document.getElementById or document.querySelector to apply focus (or jQuery equivalents).

Markup is standard HTML/angular directives with added id/classes for selection

<input id="myInput" type="text" ng-model="myInputModel" />

Controller broadcasts event

$scope.$emit('ui:focus', '#myInput');

In UI service uses querySelector - if there are multiple matches (say due to class) it will only return the first

$rootScope.$on('ui:focus', function($event, selector){
  var elem = document.querySelector(selector);
  if (elem) {
    elem.focus();
  }
});

You may want to use $timeout() to force a digest cycle

johans
  • 1,674
  • 12
  • 7
0

Just throwing in some coffee.

app.directive 'ngAltFocus', ->
    restrict: 'A'
    scope: ngAltFocus: '='
    link: (scope, el, attrs) ->
        scope.$watch 'ngAltFocus', (nv) -> el[0].focus() if nv
Brooklyn Nicholson
  • 646
  • 10
  • 25
0

Not sure if relying on the timeout is a good idea, but this works for ng-repeat because this code runs AFTER angularjs updates the DOM, so you make sure all objects are there:

myApp.directive('onLastRepeat', [function () {
        return function (scope, element, attrs) {
            if (scope.$last) setTimeout(function () {
                scope.$emit('onRepeatLast', element, attrs);
            }, 1);
        };
    }]);
    //controller for grid
    myApp.controller('SimpleController', ['$scope', '$timeout', '$http', function ($scope, $timeout, $http)
    {
        var newItemRemoved = false;
        var requiredAlert = false;
        //this event fires up when angular updates the dom for the last item
        //it's observed, so here, we stop the progress bar
        $scope.$on('onRepeatLast', function (scope, element, attrs) {
            //$scope.complete();
            console.log('done done!');
            $("#txtFirstName").focus();
        });
    }]);
Alex
  • 2,247
  • 1
  • 27
  • 37
0

you can use below directive that get a bool value in html input for focus on it...

//js file
angular.module("appName").directive("ngFocus", function () {
       return function (scope, elem, attrs, ctrl) {
             if (attrs.ngFocus === "true") {
                 $(elem).focus();
             }
             if (!ctrl) {
                 return;
             }
       elem.on("focus", function () {
            elem.addClass("has-focus");
            scope.$apply(function () {
                ctrl.hasFocus = true;
            });
        });
    };
});

<!-- html file -->
<input type="text" ng-focus="boolValue" />

You can even set a function in your controller to ngFocus directive value pay attention to below code...

<!-- html file -->
<input type="text" ng-focus="myFunc()" />


//controller file
$scope.myFunc=function(){
      if(condition){
          return true;
      }else{
          return false;
      }
}

this directive to happen when html page render.

pejman
  • 740
  • 7
  • 13
0

It's easy.. try this

html

<select id="ddl00">  
 <option>"test 01"</option>  
</select>

javascript

document.getElementById("ddl00").focus();
isanka thalagala
  • 456
  • 2
  • 10
  • 22
0

Programmatically call any action on element: click(), focus(), select()...

Usage:

<a href="google.com" auto-action="{'click': $scope.autoclick, 'focus': $scope.autofocus}">Link</a>

Directive:

/**
 * Programatically Triggers given function on the element
 * Syntax: the same as for ng-class="object"
 * Example: <a href="google.com" auto-action="{'click': autoclick_boolean, 'focus': autofocus_boolean}">Link</a>
 */
app.directive('focusMe', function ($timeout) {
    return {
        restrict: 'A',
        scope: {
            autoAction: '<',
        },
        link: function (scope, element, attr) {
            const _el = element[0];
            for (const func in scope.autoAction) {
                if (!scope.autoAction.hasOwnProperty(func)) {
                    continue;
                }
                scope.$watch(`autoAction['${func}']`, (newVal, oldVal) => {
                    if (newVal !== oldVal) {
                        $timeout(() => {
                            _el[func]();
                        });
                    }
                });
            }

        }
    }
});

To address this question set the variable on initialization (preferably) in controller or as ng-init:

 <input ng-init="autofocus=true" auto-action="{'focus': autofocus}">
Mesco
  • 1,254
  • 13
  • 21