Set element focus in angular way

242,809

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.

DEMO

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");
Share:
242,809

Related videos on Youtube

Tiago
Author by

Tiago

Updated on August 22, 2020

Comments

  • Tiago
    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
    user1821052 over 9 years
    I 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
    ryeballar over 9 years
    It 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
    user1821052 over 9 years
    Thanks! For those wondering where this is referenced in the angular docs, here's the link (took me forever to find)
  • ryeballar
    ryeballar over 9 years
    what 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 when myCondition is already true, your code watches the changes for the attribute focusOnCondition but it will not trigger when the value you try to change is still the same.
  • Braulio
    Braulio over 9 years
    I'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
    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
    ryeballar almost 9 years
    @PratikGaikwad can you provide a use case? You're question is quite vague. State an example.
  • Pratik Gaikwad
    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
    ryeballar almost 9 years
    Then 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
    Gaurav Gandhi over 8 years
    Only thing works is this ! Kudos. i wanted something i can use to focus on radio buttons, with hotkeys. This thing only worked. TY.
  • mortb
    mortb about 8 years
    Seems like a generic solution. Better than depending on ids. I like it.
  • John Rix
    John Rix about 8 years
    Note: 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
    forgivenson about 8 years
    This 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
    Adrian Carr almost 8 years
    In case anyone else tries this and it doesn't work, I had to change element.focus(); to element[0].focus();
  • setec
    setec almost 8 years
    This solution is much more 'angular way' than id-based hack above.
  • Garret Wilson
    Garret Wilson over 6 years
    That'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
    cstricklan over 6 years
    Glad 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 explicitly false and not just falsey so the directive will still work if that attribute isn't defined.
  • Bruno Santos
    Bruno Santos about 6 years
    It's insane the amount of work that's necessary in angular just to focus an input.
  • Atomosk
    Atomosk over 5 years
    Controller-as is much simplier with lodash. _.set(scope, attributes.focusOnSaveInput, function() { element.focus(); }).
  • David Casillas
    David Casillas over 5 years
    Is accessing the DOM in a Service the Angular way of doing things? Shouldn't that be restricted to directives?