如何设置输入框的焦点?

774

什么是在AngularJS中设置输入框焦点的'Angular方式'?

更具体的要求:

  1. 当打开Modal时,在此Modal内预定义的<input>上设置焦点。
  2. 每次<input>变为可见(例如通过单击某个按钮),都将其设置为焦点。

我尝试使用autofocus实现第一个要求, 但这只在Modal第一次打开时,且只在某些浏览器中有效(例如在Firefox中无效)。

33个回答

591
  1. 打开模态框时,将焦点设置在预定义的模态框内<input>元素上。

定义一个指令并让它 $watch 一个属性/触发器,以便知道何时将焦点放在该元素上:

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

在渲染模态框时,似乎需要使用 $timeout。

'2.' 每次 <input> 可见(例如通过单击某个按钮),都将焦点设置在它上面。

创建一个类似上面的指令。监视一些作用域属性,当它变为 true 时(在 ng-click 处理程序中设置它),执行 element[0].focus()。根据您的用例,您可能需要或不需要 $timeout:

<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


2013年7月更新:我看到一些人使用我的原始隔离作用域指令,然后在嵌入式输入字段(即模态框中的输入字段)中遇到问题。一个没有新作用域(或可能是新的子作用域)的指令应该可以减轻一些痛苦。所以我更新了答案,不再使用隔离作用域。以下是原始答案:

1. 使用隔离作用域的原始答案:

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的原始答案,使用隔离作用域:

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

由于我们需要在指令中重置trigger/focusInput属性,所以使用'='进行双向数据绑定。在第一个指令中,'@'已经足够了。此外,请注意,在使用'@'时,我们将触发值与"true"进行比较,因为'@'始终会生成字符串。


2
请参考@Josh的“focus”指令:https://dev59.com/rWUq5IYBdhLWcg3wBL3j#14859639。他在实现中没有使用隔离作用域。 - Mark Rajcok
3
@MarkRajcok 我只是好奇:这个版本可以工作,但如果我在输入字段上设置一个 ng-model,当我使用具有隔离作用域的这个指令时模型值会丢失。如果我尝试没有隔离作用域的Josh版本,则不会出现此问题。作为一名新手,我希望能理解其中的区别。这是一个Plunker演示了它。 - Sunil D.
2
我发现 #1 在 AngularJS 1.0.6 中非常有效。然而,在调试器下运行时,我注意到每次关闭并重新打开我的模态框时,我都会看到比上一次多一个调用设置焦点的函数。我稍微修改了那个函数,以在 value != "true" 时解除绑定观察器,并且这似乎解决了我的问题。 - Andrew Brown
3
如果attrs.focusMe是一个表达式而不仅仅是一个变量名,则模糊事件“修复”失败(model.assign不是一个函数)。例如,它可以使用:focus-me="pageActive"但在以下情况下会失败:focus-me="pageActive==true" - Tom Mettam
4
对于我来说,这段代码是有效的,我可以看到它正确执行,并且元素[0]是正确的控件。然而,.focus()不能触发焦点。可能是Bootstrap或其他东西在干扰吗? - Sonic Soul
显示剩余20条评论

269

##(EDIT: 下面解释中有更新的解决方案)

Mark Rajcok是个好人,他的答案是有效的,但是它存在缺陷(抱歉Mark)...

...尝试使用布尔变量来聚焦于输入框,然后模糊该输入框,再次尝试使用它来聚焦于输入框。这样不行,除非您将该布尔变量重置为false,然后$digest,然后将其重新设置为true。即使在表达式中使用字符串比较,也必须强制更改字符串为其他内容,$digest然后再将其改回。 (这已经通过模糊事件处理程序得到了解决。)

因此,我提出这个备选方案:

使用事件,Angular中被遗忘的特性。

JavaScript非常喜欢事件。事件本质上是松耦合的,而且更好的是,你避免了向你的$digest添加另一个$watch。

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

现在你可以像这样使用它:

<input type="text" focus-on="newItemAdded" />

然后在应用程序中的任何位置都可以...

$scope.addNewItem = function () {
    /* stuff here to add a new item... */

    $scope.$broadcast('newItemAdded');
};

这很棒,因为你可以用类似的东西做各种事情。首先,你可以连接已经存在的事件。其次,通过让应用程序的不同部分发布其他部分可以订阅的事件,你可以开始做一些聪明的事情。

无论如何,这种类型的东西对我来说都是“事件驱动”的。我认为作为 Angular 开发人员,我们会尽力将 $scope 形状的楔子敲入事件形状的孔中。

这是最好的解决方案吗?我不知道。它是一个解决方案。


更新的解决方案

在@ShimonRachlenko的下方评论后,我稍微改变了我的方法。现在我使用一个服务和一个指令的组合,在“幕后”处理事件:

除此之外,它与上面概述的原则相同。

这里有一个快速演示 Plunk

###用法

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

3
如果你希望这个控制器在进入时生效,那么你需要用 $timeout 包装对 $broadcast 的调用。否则,这是一个不错的解决方案。 - Shimon Rachlenko
@ShimonRachlenko - 谢谢。但我不确定你所说的$timeout是什么意思。如果我想在控制器构造函数被处理时广播,我会立即进行广播。使用timeout只会将广播推迟到事件循环中稍后执行,没有其他作用。 - Ben Lesh
1
是的,这就足以让指令初始化。否则事件会在指令开始监听之前广播。再次强调,只有当您想在进入页面时触发指令时才需要这样做。 - Shimon Rachlenko
2
你说得对。我猜我没有用它来专注于负载。我会用更强大的东西更新答案。 - Ben Lesh
1
这绝对是最优雅的“angular方式”解决方案。虽然我第一次遇到这个问题时大多只是复制了代码,但我很高兴你为它制作了一个模块!我真诚地认为将其纳入核心Angular可能值得尝试。 - Randolpho
显示剩余2条评论

244

我发现其他答案有些过于复杂了,其实你只需要这个就行了。

app.directive('autoFocus', function($timeout) {
    return {
        restrict: 'AC',
        link: function(_scope, _element) {
            $timeout(function(){
                _element[0].focus();
            }, 0);
        }
    };
});

使用方法为

<input name="theInput" auto-focus>

我们使用超时时间来让DOM中的元素渲染,即使为零,它也至少等待那个时间 - 这样就可以在模态框等情况下正常工作。


1
有几种方法可以做到这一点,其中一种可能非常直接和简单的方法是在作用域(这里是控制器)上设置单击按钮时要聚焦的项目的ID,然后在指令中简单地监听此项。在这种情况下,您不必将指令放置在特定位置,只需在该页面的某个位置即可(我过去曾成功使用这些类型的监视器指令,特别是关注事物和滚动事物)。然后,如果您正在使用jquery(这将使其更简单),只需找到该元素ID并将其聚焦。 - ecancil
14
如果输入位于弹出模态框中,那么零超时的解决方案对我不起作用。但是即使超时只有10毫秒也可以解决问题。 - Eugene Fidelin
@ecancil:我喜欢你的方法,因为它最简单,但是在IE中你需要将超时设置为~500ms,因为模态出现的动画会导致输入框外出现闪烁的光标。我不知道有没有一种好的方式可以在动画结束时发生这种情况,所以我用500毫秒来强制执行它。 - Dev93
如果您需要从数据库中获取更多数据,则无法工作,因此需要等待控制器完成数据提取,因此请在0的位置添加足够的时间。 - Ali Adravi
2
对于像@Ade这样想要在简单的ng-click中使用它的人:假设点击按钮时在您的输入上有ng-click="showInput = !showInput"。然后,在您的实际输入中,添加ng-if="showInput"。切换按钮将使指令每次重新运行。我曾经遇到过这个问题,因为我使用了错误的方法ng-show - JVG
这个方法对我有效,就像Jezz建议的那样,加上了500毫秒的延迟。我正在使用它与bootbox/bootstrap模态框。 - Paul Speranza

94

33
很不幸,这种方法只有第一次使用时(如果有用的话)才有效。我在一个Angular“编辑即时”情况下使用过,第一次在Chrome中效果非常好,但在Firefox中完全没有效果。在Chrome中,当我点击另一个项目以进行编辑或者再次点击同一项目时,它就不再获得焦点了。文档说明它在页面加载时起作用,而在Angular应用程序中只会发生一次。如果事情能这么简单,我们都可以坐在海滩上赚取20%的收益! - Nocturno

63

您也可以使用内置在angular中的jqlite功能。

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


2
没有加载jQuery:angular.forEach(document.querySelectorAll('.selector'), function(elem) { elem.focus(); }); - Dan Benamy
29
把这个放到控制器里不是一个好的做法,对吗? - VitalyB
2
如果将这个 jqlite 行放在控制器内是不好的实践,那么将这个 jqlite 行放在指令内会更好吗? - sports
3
我明白了:“通过选择器查找元素在 jqLite 中不被支持!” - Maria Ines Parnisari
1
@VitalyB 我曾经遇到这样一个情况,我需要在ajax调用后模糊一个元素。我可以在控制器内的回调函数中添加一行纯JS代码,也可以专门为该输入框构建一个完整的指令来实现模糊效果。但我觉得这太麻烦了,所以我选择了第一种选项。 - Sebastianb
显示剩余2条评论

56

这很好用,是一种集成Angular的焦点控制输入框的方式。

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

尽管这不是一个纯粹的Angular方式来完成任务,但语法遵循Angular风格。jQuery在Angular中起到了直接和间接地访问DOM的作用(jQLite => JQuery Light)。

如果需要,这段代码可以很容易地放入一个简单的Angular指令中,其中元素是直接可访问的。


我不确定这是否是ViewModel应用程序的好方法。在Angular中,必须通过指令来完成。 - Agat
如果有人更改了文本框A,您需要显示弹出窗口并将焦点设置在另一个文本框B上。 - Sanjeev Singh
1
当我使用这个时,出现了这个错误:Looking up elements via selectors is not supported by jqLite! - JVG
6
据我所知,您需要首先加载jQuery作为一个依赖项,这样angular.element就成为了$() / jQuery()的包装器。因此,如果没有这个,它将无法工作,而且基本上您仍然在使用jQuery(但如果我错了请纠正我)。 - JVG
@Jascination:jqLite是为了去除jQuery依赖而开发的。你不需要整个jQuery来访问DOM中的元素。但如果你已经安装了jQuery,那么Angular会引用jQuery。请查看此链接:http://docs.angularjs.org/api/angular.element - Sanjeev Singh

30

我认为在创建元素时,$timeout 不是一个好的聚焦方式。这里有一种使用内置的 Angular 功能的方法,可以从 Angular 文档中挖掘出来。请注意,“link”属性可以分成“pre”和“post”,用于 pre-link 和 post-link 函数。

工作示例: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 />

完整的AngularJS指令文档:https://docs.angularjs.org/api/ng/service/$compile


(注:该链接为英文原文)

2
我不得不在$timeout中包装element[0].focus(),才能让它对我起作用。 - hussainb
@bbodenmiller 我的模态框应用了淡出效果,当元素被构建时它是不可见的(100%透明),因此浏览器会默默地阻止焦点调用。显然,经过一些毫秒的时间可以让我们将焦点放在几乎透明但可见的输入/按钮上。 - snowindy
2
根据文档,“link”函数默认为“postLink”。另请参阅:http://www.bennadel.com/blog/2746-the-post-link-function-is-the-link-function-in-angularjs-directives.htm - jody tate

18

这是我的原始解决方案:

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

并且HTML代码如下:

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

它的作用:

在使用ng-show时,它可以将输入框聚焦。这里没有使用$watch或$on。


实际上,我相信 {{ isVisible }} 无论如何都会创建一个监视器,因此“不使用 $watch”语句是不正确的。 - masimplo
是的,我认为你是对的。但它仍然是一种简单的方法来完成它。 - Edhowler
你仍然需要timeout()。 - James

17
我最近编写了一个双向绑定的焦点指令,类似于model。
你可以像这样使用焦点指令:
<input focus="someFocusVariable">

如果您在控制器的任何地方将someFocusVariable作为范围变量true,则输入框会获得焦点。如果您想要“失去焦点”,则可以将someFocusVariable设置为false。这就像Mark Rajcok的第一个答案,但具有双向绑定。

以下是指令:

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

使用方法:

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

这是代码演示:

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


这应该是正确的答案。 它涵盖了所有用例。 - Kunal

11

对于那些使用Bootstrap插件的Angular用户:

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

您可以钩入模态实例的opened承诺:

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

modalInstance.result.then(function ( etc...

很好!然而在我的情况下,需要使用50ms$timeout而不是0 - lk_vc

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