【问题标题】:Set element focus in angular way以角度方式设置元素焦点
【发布时间】:2014-10-25 03:00:43
【问题描述】:

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

考虑到 jquery 方式,但想以角度方式做到这一点,我提出了一个解决方案,我们使用元素的 id 在任何函数中设置焦点,所以,由于我在角度方面非常新,我想得到一些意见,如果这种方式是正确的,有问题,无论如何,任何可以帮助我在角度上更好地做到这一点的东西。

基本上,我创建一个指令来监视用户使用指令或默认的 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";
}

这是一个好的角度解决方案吗?它是否存在我尚未看到的糟糕体验的问题?

【问题讨论】:

    标签: angularjs focus setfocus


    【解决方案1】:

    您的解决方案的问题在于,当绑定到创建新范围的其他指令时,它无法正常工作,例如ng-repeat。更好的解决方案是简单地创建一个服务函数,使您能够在控制器中强制聚焦元素或在 html 中以声明方式聚焦元素。

    DEMO

    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>
    

    【讨论】:

    • 我真的很喜欢这个解决方案。但是,您能解释一下使用 $timeout 的原因吗?您使用它的原因是“Angular Thing”还是“DOM Thing”?
    • 它确保它在 angular 执行的任何摘要周期之后运行,但这不包括在超时后执行的异步操作后受影响的摘要周期。
    • 谢谢!对于那些想知道在 Angular 文档中哪里引用它的人,这里是 link(花了我很长时间才找到)
    • @ryeballar,谢谢!。不错的简单解决方案。不过只是一个问题。我可以使用通过属性创建的工厂而不是等待某些事件发生吗?
    • 仅仅为了聚焦输入就需要在角度上做大量工作。
    【解决方案2】:

    关于这个解决方案,我们可以创建一个指令并将其附加到 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">
    

    【讨论】:

    • myCondition $scope 变量已经设置为true,然后用户选择聚焦到另一个元素时会发生什么,当myCondition已经为true时,你还能重新触发焦点吗?您的代码会监视属性 focusOnCondition 的更改,但当您尝试更改的值仍然相同时,它不会触发。
    • 我将更新示例,在我们的例子中,我们有两个单选按钮,我们根据值将标志切换为 true 或 false,您只需将 myCondition 标志更改为 true 或假
    • 似乎是一个通用的解决方案。比依赖 id 好。我喜欢它。
    • 万一其他人尝试这个但它不起作用,我不得不改变 element.focus();到元素[0].focus();
    • 这个解决方案比上面基于 id 的 hack 更“角度”。
    【解决方案3】:

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

    这是将其附加到范围的简化方法。请参阅完整的 sn-p 以了解如何处理 controller-as 语法。

    指令:

    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>

    已编辑以提供有关此方法的原因的更多解释,并扩展代码 sn-p 以供控制器使用。

    【讨论】:

    • 这很好,对我来说效果很好。但是现在我有一组使用ng-repeat 的输入,我只想为第一个设置焦点功能。知道如何根据$index 有条件地为&lt;input&gt; 设置焦点函数吗?
    • 很高兴它很有用。我的角度 1 有点生疏了,但是您应该能够在输入中添加另一个属性,例如 assign-focus-function-if="{{$index===0}}",然后作为指令的第一行在分配函数之前提前退出(如果不是这样):if (attr.assignFocusFunctionIf===false) return;请注意,我正在检查它是否是明确的 false 而不仅仅是错误的,因此如果未定义该属性,该指令仍然有效。
    • Controller-as 使用 lodash 更简单。 _.set(scope, attributes.focusOnSaveInput, function() { element.focus(); }).
    【解决方案4】:

    你可以试试

    angular.element('#<elementId>').focus();
    

    例如。

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

    它对我有用。

    【讨论】:

    • 注意:这只有在使用完整的 jQuery 而不是依赖嵌入在 Angular 中的 jqLit​​e 时才有效。见docs.angularjs.org/api/ng/function/angular.element
    • 这是 jQuery 执行此操作的方式,而不是角度方式。该问题专门询问如何以有角度的方式进行。
    【解决方案5】:

    另一个选择是使用 Angular 的内置 pub-sub 架构来通知你的指令集中。与其他方法类似,但它不直接与属性绑定,而是监听特定键的范围。

    指令:

    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");
    

    【讨论】:

      【解决方案6】:

      我更喜欢使用表达式。这让我可以做一些事情,比如当字段有效、达到一定长度时,当然还有加载后,关注按钮。

      <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://stackoverflow.com/a/29963695/937997

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-28
        • 2018-10-01
        相关资源
        最近更新 更多