以 Angular 方式设置元素焦点

113

在寻找如何使用Angular设置焦点元素的示例后,我发现大多数人使用某些变量来监视并设置焦点,并且他们使用一个不同的变量来设置每个想要设置焦点的字段。 在一个有很多字段的表单中,这意味着有很多不同的变量。

考虑到jquery的方式,但希望以angular的方式实现,我创建了一种解决方案,可以在任何函数中使用元素的id来设置焦点,因此,由于我刚开始接触angular,我想得到一些意见,以确定这种方法是否正确,是否存在问题,或者任何其他有助于更好地使用Angular进行此操作的东西。

基本上,我创建了一个指令,该指令监视由用户使用指令定义的作用域值,或者默认的focusElement,并且当该值与元素的id相同时,该元素会自动设置焦点。

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

有些情况下需要将焦点放在某个输入框中,可以按照以下方式进行操作

<input my-focus id="input1" type="text" />

在这里可以设置任何需要焦点的元素:

<a href="" ng-click="clickButton()" >Set focus</a>

以及设置焦点的示例函数:

$scope.clickButton = function() {
    $scope.focusElement = "input1";
}

这是一个好的Angular解决方案吗?它有什么问题,是由于我的经验不足而我还没有看到的吗?

6个回答

173
你的解决方案存在问题,当与其他创建新作用域的指令(例如ng-repeat)绑定时,它不能很好地工作。更好的解决方案是简单地创建一个服务函数,在控制器中使元素具有命令性焦点或在html中声明性地使元素具有焦点。 演示 JAVASCRIPT 服务
 .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('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('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>

3
谢谢!对于那些想知道在 Angular 文档中引用了这个内容的人,这是链接(花了我很长时间才找到):https://docs.angularjs.org/error/$rootScope/inprog - user1821052
@ryeballar,谢谢!很好的简单解决方案。只是有一个问题。我可以使用通过属性创建的工厂,而不是等待某些事件发生吗? - Pratik Gaikwad
@PratikGaikwad,你能提供一个使用案例吗?你的问题相当模糊。请举个例子。 - ryeballar
那么在这种情况下,你将如何触发焦点呢?根据提供的声明性HTML,它可能是一个单击事件或任何可以从按钮触发的事件。但在您提供的情况下,将会触发什么呢? - ryeballar
4
在 Angular 中,仅仅是让一个输入框获得焦点就需要投入大量的工作,这简直让人发疯。 - Bruno Santos
显示剩余5条评论

19
关于这个解决方案,我们可以创建一个指令并将其附加到满足特定条件时需要获得焦点的DOM元素上。通过采用这种方法,我们避免了控制器与DOM元素ID之间的耦合。
示例代码指令:
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();
                        });
                    }
                });
            }
        };
    }
]);

一个可能的用法

.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">

1
myCondition $scope变量已经设置为true,并且用户选择将焦点转移到另一个元素时,会发生什么情况?即使myCondition已经是true,您是否仍然可以重新触发焦点?您的代码会监视属性focusOnCondition的更改,但如果您尝试更改的值仍然相同,它不会触发。 - ryeballar
我将更新示例,在我们的情况下,我们有两个单选按钮,并根据值将标志切换为true或false,您只需将myCondition标志更改为true或false。 - Braulio
看起来是一个通用的解决方案。比依赖于ID要好。我喜欢它。 - mortb
如果其他人尝试此操作并且不起作用,我必须将 "element.focus();" 更改为 "element[0].focus();"。 - Adrian Carr
1
这个解决方案比上面基于ID的hack更符合Angular的方式。 - setec

11

我喜欢尽可能避免DOM查找、监视器和全局触发器,因此我采用更直接的方法。使用指令来分配一个简单函数,该函数专注于指令元素。然后在控制器范围内需要调用该函数。

这里是将其附加到作用域的简化方法。有关处理控制器作为语法的完整片段,请参见完整的片段。

指令:

app.directive('inputFocusFunction', function () {
    'use strict';
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            scope[attr.inputFocusFunction] = function () {
                element[0].focus();
            };
        }
    };
});

还有在HTML中:

<input input-focus-function="focusOnSaveInput" ng-model="saveName">
<button ng-click="focusOnSaveInput()">Focus</button>

或者在控制器中:

$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>

编辑以提供更多有关这种方法的解释,并扩展控制器使用的代码片段。


这很好,对我一直运作良好。但现在我有一组使用 ng-repeat 的输入,我只想为第一个设置焦点函数。您有什么想法如何根据 $index 条件性地为 <input> 设置焦点函数? - Garret Wilson
很高兴它有用。我的Angular 1有点生疏,但你应该能够向输入添加另一个属性,例如assign-focus-function-if="{{$index===0}}",然后在指令的第一行提前退出分配函数,如果不是这样的话:if (attr.assignFocusFunctionIf===false) return;请注意,我正在检查它是否明确为“false”,而不仅仅是falsey,所以如果未定义该属性,指令仍将起作用。 - cstricklan
使用lodash,控制器-as变得更加简单。_.set(scope, attributes.focusOnSaveInput, function() { element.focus(); }) - Atomosk

9
你可以尝试。
angular.element('#<elementId>').focus();

for eg.

angular.element('#txtUserId').focus();

它对我有效。


4
注意:只有在使用完整的jQuery而不是依赖于嵌入在Angular中的jqLite时,此方法才有效。请参阅https://docs.angularjs.org/api/ng/function/angular.element。 - John Rix
4
这是用jQuery的方法实现,而不是Angular的方法。问题明确要求如何用Angular的方式来实现。 - forgivenson

4
另一种选择是使用Angular内置的发布-订阅架构来通知您的指令进行聚焦。与其他方法类似,但它不直接绑定到属性,而是在其作用域上监听特定的键。
指令:
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" />

控制器:

//Assume this is within your controller
//And you've hit the point where you want to focus the input:
$scope.$broadcast("focusTextInput");

3

我更喜欢使用表达式。这样可以让我在字段有效、达到一定长度和加载后等情况下,专注于按钮。

<button type="button" moo-focus-expression="form.phone.$valid">
<button type="submit" moo-focus-expression="smsconfirm.length == 6">
<input type="text" moo-focus-expression="true">

在复杂的表单中,这也减少了创建额外作用域变量以进行聚焦的需求。
请参见https://dev59.com/YGUq5IYBdhLWcg3wC8Lx#29963695

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接