26

I have places in my code where I have this:

<input data-ng-disabled="SOME_SCOPE_VARIABLE" />

I would like to be able to use it like this too:

<input data-ng-autofocus="SOME_SCOPE_VARIABLE" />

Or even better, mimicking how ng-style is done:

<input data-ng-attribute="{autofocus: SOME_SCOPE_VARIABLE}" />

Does this exist in the current version of AngularJS? I noticed in the code there's a BOOLEAN_ATTR which gets all the attr's that AngularJS supports. I don't want to modify that in fear of changing versions and forgetting to update.

Tushar
  • 85,780
  • 21
  • 159
  • 179
Mathew Berg
  • 28,625
  • 11
  • 69
  • 90

10 Answers10

43

Update: AngularJS now has an ngFocus directive that evaluates an expression on focus, but I mention it here for the sake of completeness.


The current version of AngularJS doesn't have a focus directive, but it's in the roadmap. Coincidentally, we were talking about this on the mailing list yesterday, and I came up with this:

angular.module('ng').directive('ngFocus', function($timeout) {
    return {
        link: function ( scope, element, attrs ) {
            scope.$watch( attrs.ngFocus, function ( val ) {
                if ( angular.isDefined( val ) && val ) {
                    $timeout( function () { element[0].focus(); } );
                }
            }, true);

            element.bind('blur', function () {
                if ( angular.isDefined( attrs.ngFocusLost ) ) {
                    scope.$apply( attrs.ngFocusLost );

                }
            });
        }
    };
});

Which works off a scope variable as you requested:

<input type="text" ng-focus="isFocused" ng-focus-lost="loseFocus()">

Here's a fiddle: http://jsfiddle.net/ANfJZ/39/

Josh David Miller
  • 120,525
  • 16
  • 127
  • 95
  • Just a note josh - you don't have to do `scope.$apply(function() { scope.$eval(expr); });` - $apply tries to evaluate what you give it so you can just do `scope.$apply(expr);`https://github.com/angular/angular.js/blob/master/src/ng/rootScope.js#L595 – Andrew Joslin Feb 13 '13 at 17:47
  • @AndyJoslin Doh! It's not often I use `$apply` with an expression... Updated. Thanks! :-) – Josh David Miller Feb 13 '13 at 17:57
  • Interesting... it seems we were both writing a focus directive at about the same time: http://stackoverflow.com/a/14837021/215945 I opted for an isolate scope, and I did not add a focus-lost capability. I'd like to pick your brain as to why you decided to not create a new or isolate scope. (I'm trying to come up with some guidelines about when to use an isolate scope, a new scope, or no new scope for directives.) – Mark Rajcok Feb 14 '13 at 02:05
  • @MarkRajcok I didn't even see that other thread! I didn't create an isolate scope because interpolation and expression evaluation in other directives on the same element may no longer work as expected. But I actually didn't even create a child scope because the directive doesn't really touch anything; it only reads what was passed to the ngFocus attr and executes the expression provided to ngFocusLost. It doesn't seem like there is a security concern here. A child scope wouldn't hurt, but I'm honestly not sure which way is better. Probably a child scope...yeah, a child scope. – Josh David Miller Feb 14 '13 at 05:18
  • 2
    @Josh, FYI, I just added a SO question about how to decide which kind of scope to use when writing a directive: http://stackoverflow.com/questions/14914213/when-writing-a-directive-how-do-i-decide-if-a-need-no-new-scope-a-new-child-sc – Mark Rajcok Feb 16 '13 at 19:29
  • @JoshDavidMiller, can you please help me in implementing your method of auto-focus when textbox is in a modal plnkr.co/WKq83K, here is the question http://stackoverflow.com/questions/17599504/why-focus-on-textbox-is-not-set-inside-a-tweeter-bootstrap-modal-popup-by-angu – Watt Jul 11 '13 at 20:15
  • 7
    Heads up: `ng-focus` is now a [built in directive](http://docs.angularjs.org/api/ng.directive:ngFocus) in Angular for binding callbacks to the `focus` event of an element. The directive here conflicts with its name – Simen Echholt Dec 04 '13 at 16:34
  • 9
    Do you guys know that there is a native html-attribute called autofocus. It works great, but would need some extension in angular, so that it works with updating the route. – Himmators Mar 11 '14 at 09:33
  • 9
    `ng-focus` (the official one) does not perform autofocus. It evaluates an expression when the element receives focus. It's ok that it's mentioned in this answer but it doesn't address the OP's question. – Keith Sep 26 '14 at 18:47
  • @Keith - You're right. I mentioned it because it had the same name as the one I wrote and that would be bad. I updated the answer to be a little clearer. – Josh David Miller Sep 26 '14 at 18:51
  • 4
    this show us, that we should never use `ng` prefix for custom directives – vp_arth Dec 10 '14 at 12:14
  • 1
    A bit of warning: Calling `element[0].focus()` like in the example will **not** necessarily trigger the soft keyboard in Safari on iOS 8 (it can only be opened as a result of a user action). But Safari will still count the element as "having focus", which means that if the user clicks on the input the soft keyboard will not appear as expected since the element is "already focused". The user needs to clicks somewhere else and _then_ click on the input for the soft keyboard to appear. – Strille Feb 25 '15 at 13:46
  • @Strille I'm looking for a way to focus on IOS devices. can I use the click() function to do so? **edit:** I tried that, didn't work. HELP!!! – Hike Nalbandyan Aug 16 '17 at 10:59
  • In Safari I don't think it's possible, but if you are using Cordova, you can set [KeyboardDisplayRequiresUserAction](https://cordova.apache.org/docs/en/latest/config_ref/index.html) to false. – Strille Aug 18 '17 at 08:16
15

You can do this with the built-in ngAttr attribute bindings.

<input ng-attr-autofocus="{{SOME_SCOPE_VARIABLE}}">

The autofocus attribute will be added if SOME_SCOPE_VARIABLE is defined (even if it's false), and will be removed if it's undefined. So I force falsy values to be undefined.

$scope.SOME_SCOPE_VARIABLE = someVar || undefined;
Chad von Nau
  • 4,316
  • 1
  • 23
  • 34
5

This directive should do the trick:

angular.module('utils.autofocus', [])
.directive('autofocus', ['$timeout', function($timeout) {
  return {
    restrict: 'A',
    scope: {'autofocus':'='}
    link : function($scope, $element) {
      $scope.$watch 'autofocus', function(focus){
        if(focus){
          $timeout(function() {
            $element[0].focus();
          });
        }
      }
    }
  }
}]);

Taken from here: https://gist.github.com/mlynch/dd407b93ed288d499778

Santiago Angel
  • 1,127
  • 15
  • 19
1
scope.doFocus = function () {
                $timeout(function () {
                        document.getElementById('you_input_id').focus();
                    });
            };
Nitya Kumar
  • 967
  • 8
  • 14
  • 2
    This answer turned up in the low quality review queue, presumably because you don't provide any explanation of the code. If this code answers the question, consider adding adding some text explaining the code in your answer. This way, you are far more likely to get more upvotes — and help the questioner learn something new. – lmo Sep 05 '16 at 21:38
1

Create a directive like this

.directive('autoFocus', ['$timeout', function ($timeout) {
        return {
            restrict: 'A',
            link: function ($scope, $element) {
                $timeout(function () {
                    $element[0].focus();
                });
            }
        }



<input type="text" auto-focus class="form-control msd-elastic" placeholder="">
byteC0de
  • 5,153
  • 5
  • 33
  • 66
0

What I did is using regular autofocus on my inputs: <input autofocus>

And then I set the focus on the first visible input with autofocus when angular is ready:

angular.element(document).ready(function() {
  $('input[autofocus]:visible:first').focus();
});

Hope this helps.

Dorian
  • 22,759
  • 8
  • 120
  • 116
  • 2
    This seems wrong on so many levels... First it should be a directive as it's working with DOM itself and second browsers already implement autofocus functionality. – Robert Koritnik Mar 18 '14 at 21:40
  • 4
    One of the anti-patterns of angular. – Ronald91 Apr 22 '14 at 19:21
  • 2
    I do what works, and couldn't find an other way to make it work fine. The autofocus by browsers is buggy, depends on each browser, and doesn't work with multiple / dynamic element with autofocus – Dorian Apr 22 '14 at 19:33
  • 2
    agreed with dorian, altough I use $timeout to wrap elm[0].focus(); – Maarten Jun 26 '14 at 11:28
  • This may have been downvoted several times, but it worked great when I had two elements and which should be autofocused depended on an ng-show directive (the first element worked, but the second wouldn't when the first was hidden). – Brandon Barkley Oct 30 '17 at 02:58
0

I did it with two custom directives, something like this:

(function(angular) {
  'use strict';

  /* @ngInject */
  function myAutoFocus($timeout) {
    return {
      restrict: 'A',
      link: function(scope, element) {
        $timeout(function() {
          element[0].focus();
        }, 300);
      }
    };
  }

  function myFocusable() {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        var focusMethodName = attrs.myFocusable;
        scope[focusMethodName] = function() {
          element[0].focus();
        };
      }
    };
  }

  angular
    .module('myFocusUtils', [])
    .directive('myAutoFocus', myAutoFocus)
    .directive('myFocusable', myFocusable);

}(angular));

If you add attribute my-auto-focus to an element, it will receive focus after 300ms. I set the value to 300 instead of 0 to let other async components to load before setting the focus.

The attribute my-focusable will create a function in the current scope. This function will set focus to the element when called. As it creates something in the scope, be cautious to avoid overriding something.

This way you don't need to add something to Angular's digest cycle (watch) and can do it entirely in the view:

<input my-focusable="focusOnInput"></input>        
<button ng-click="focusOnInput()">Click to focus</button>

I created a JSFiddle to show the myFocusable directive: http://jsfiddle.net/8shLj3jc/

For some reason I don't know, the myAutoFocus directive does not work in JSFiddle, but it works in my page.

Thiago Negri
  • 5,221
  • 2
  • 28
  • 39
0
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>

<div ng-app="myApp" ng-controller="namesCtrl"> 
  <div ng-repeat="x in names">
     <input ng-attr-focus={{$first}} value="{{x.name + ', ' + x.country }}" />
  </div>
</div>

<script>

        var myApp = angular.module('myApp', []);
        myApp.controller('namesCtrl', function($scope) {
    $scope.names = [
        {name:'x1',country:'y1'},
        {name:'x2',country:'y2'},
        {name:'x3',country:'y3'}
    ];
});

        myApp.directive("focus", function(){
            return {
                restrict: "A",
                link: function link(scope, element, attrs) {

                      if(JSON.parse(attrs.focus)){
                          element[0].focus();
                      }
                }
            };
        });
</script>
</body>
</html>

had created above custom directive for one of my use case.

  • always focusses on first input element.
  • works for ajax data, browser back/forward buttons.
  • Tested on chrome and firefox(default autofocus is not supported here)

JSON.parse is used to parse string "true" returned from html to boolean true in JS.
another way to use attrs.focus === "true" for if condition.

0

so without $timeout you can also use auto focus like this -

    <input type="text" ng-show="{{condition}}" class='input-class'></input>
    angular.element(document).ready(function(){
    angular.element('.input-class')[0].focus();
    });
0

Combining whar others mentioned above:

JS Code:

myApp.directive('ngAutofocus', ['$timeout', function ($timeout) {
    var linker = function ($scope, element, attrs) {
        $scope.$watch('pageLoaded', function (pageLoaded) {
            if (pageLoaded) {
                $timeout(function () {
                    element[0].focus();
                });
            }
        });
    };

    return {
        restrict: 'A',
        link: linker
    };
}]);

HTML:

  <input type="text" ng-model="myField" class="input-block-level edit-item" ng-autofocus>

Set pageLoaded to true from your initial load method of the page get:

    var loadData = function () {
        ..
        return $http.get(url).then(function (requestResponse) {
           $scope.pageLoaded = true;
......
}
Raghav
  • 8,772
  • 6
  • 82
  • 106