Como definir o foco no campo de input?

Qual é o ‘caminho angular’ para definir o foco no campo de input no AngularJS?

Requisitos mais específicos:

  1. Quando um Modal é aberto, defina o foco em uma predefinida dentro deste Modal.
  2. Toda vez que se torna visível (por exemplo, clicando em algum botão), defina o foco nele.

Eu tentei alcançar o primeiro requisito com autofocus , mas isso funciona apenas quando o Modal é aberto pela primeira vez, e somente em certos navegadores (por exemplo, no Firefox ele não funciona).

Qualquer ajuda será apreciada.

  1. Quando um Modal é aberto, defina o foco em uma predefinida dentro deste Modal.

Defina uma diretiva e faça com que ela assista a uma propriedade / gatilho para saber quando focalizar o elemento:

 Name:  

 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

O $ timeout parece ser necessário para dar o tempo modal para renderizar.

‘2.’ Toda vez que se torna visível (por exemplo, clicando em algum botão), defina o foco nele.

Crie uma diretiva essencialmente como a acima. Observe alguma propriedade de escopo e, quando ela se tornar verdadeira (configure-a no manipulador ng-click), execute o element[0].focus() . Dependendo do seu caso de uso, você pode ou não precisar de um tempo limite $ para este:

  
{{ myInput }}

 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


Atualização 7/2013 : Eu vi algumas pessoas usarem minhas diretivas de escopo originais isoladas e, em seguida, ter problemas com campos de input incorporados (isto é, um campo de input no modal). Uma diretiva sem novo escopo (ou possivelmente um novo escopo da criança) deve aliviar parte da dor. Então, acima eu atualizei a resposta para não usar escopos isolados. Abaixo está a resposta original:

Resposta original para 1., usando um escopo isolado:

 Name:  

 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

Resposta original para 2., usando um escopo isolado:

  

 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

Como precisamos redefinir a propriedade trigger / focusInput na diretiva, ‘=’ é usado para binding de dados bidirecional. Na primeira diretiva, ‘@’ era suficiente. Observe também que, ao usar ‘@’, comparamos o valor do acionador como “true”, pois @ sempre resulta em uma string.

(EDIT: eu adicionei uma solução atualizada abaixo desta explicação)

Mark Rajcok é o homem … e sua resposta é uma resposta válida, mas teve um defeito (desculpe Mark) …

… Tente usar o booleano para focar na input, depois desfoque a input e tente usá-la para focar a input novamente. Ele não funcionará a menos que você redefina o valor booleano para false, então $ digest, então redefina de volta para true. Mesmo se você usar uma comparação de string na sua expressão, você será forçado a mudar a string para outra coisa, $ digest, e então alterá-la de volta. (Isso foi resolvido com o manipulador de events de desfoque).

Então eu proponho esta solução alternativa:

Use um evento, o recurso esquecido do Angular.

JavaScript adora events depois de tudo. Os events são inerentemente fracamente acoplados e, melhor ainda, você evita adicionar outro $ watch ao seu $ digest.

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

Então agora você poderia usá-lo assim:

  

e depois em qualquer lugar no seu aplicativo …

 $scope.addNewItem = function () { /* stuff here to add a new item... */ $scope.$broadcast('newItemAdded'); }; 

Isso é incrível porque você pode fazer todo tipo de coisa com algo assim. Por um lado, você poderia amarrar em events que já existem. Para outra coisa, você começa a fazer algo inteligente fazendo com que partes diferentes do seu aplicativo publiquem events que outras partes do seu aplicativo podem assinar.

De qualquer forma, esse tipo de coisa grita “orientado a events” para mim. Eu acho que, como desenvolvedores Angular, tentamos arrasar os pinos em forma de escopo em forma de evento.

É a melhor solução? Não sei. É uma solução.


Solução atualizada

Após o comentário do @ ShimonRachlenko abaixo, eu mudei o meu método de fazer isso um pouco. Agora eu uso uma combinação de um serviço e uma diretiva que lida com um evento “nos bastidores”:

Fora isso, é o mesmo principal descrito acima.

Aqui está uma demonstração rápida Plunk

Uso

  
 app.controller('MyCtrl', function($scope, focus) { focus('focusMe'); }); 

Fonte

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

Eu descobri que algumas das outras respostas são muito complicadas quando tudo o que você realmente precisa é

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

o uso é

  

Usamos o tempo limite para deixar as coisas no dom render, mesmo que seja zero, pelo menos, espera por isso – dessa forma isso funciona em modais e também

O HTML possui um autofocus atributo.

  

http://www.w3schools.com/tags/att_input_autofocus.asp

Você também pode usar a funcionalidade jqlite incorporada em angular.

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

Isso funciona bem e uma maneira angular de concentrar o controle de input

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

Esta não é uma forma angular pura de executar a tarefa, mas a syntax segue o estilo angular. Jquery desempenha papel indiretamente e acessa diretamente DOM usando Angular (jQLite => JQuery Light).

Se necessário, esse código pode ser facilmente colocado dentro de uma diretiva angular simples, na qual o elemento é diretamente acessível.

Eu não acho que $ timeout seja uma boa maneira de focar o elemento na criação. Aqui está um método usando a funcionalidade angular integrada, escavada a partir das profundezas escuras dos documentos angulares. Observe como o atributo “link” pode ser dividido em “pré” e “post”, para funções de pré-link e pós-link.

Exemplo de trabalho: 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(); } } } }); 
    

Documentos da Diretriz Full AngularJS: https://docs.angularjs.org/api/ng/service/$compile

Eu escrevi uma diretiva de foco de binding bidirecional, assim como o modelo recentemente.

Você usa diretiva de foco assim:

  

Se você tornar a variável de escopo someFocusVariable verdadeira em qualquer lugar no seu controlador, a input terá o foco. E se você “borrar” sua input, someFocusVariable é definido como falso. É como a primeira resposta de Mark Rajcok, mas com binding bidirecional.

Aqui está a diretiva:

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

Uso:

 
An Input:
Focus!
someFocusVariable: {{ someFocusVariable }}
content: {{ model }}

Aqui está o violino:

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

Aqui está a minha solução original:

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

E o HTML:

   

O que faz:

Ele concentra a input conforme ela se torna visível com ng-show. Não use $ watch ou $ aqui.

Para quem usa o Angular com o plugin Bootstrap:

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

Você pode conectar-se à promise opened da instância modal:

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

Em vez de criar sua própria diretiva, é possível simplesmente usar funções javascript para realizar um foco.

Aqui está um exemplo.

No arquivo html:

  

Em um arquivo javascript, em um controlador por exemplo, onde você deseja ativar o foco:

 document.getElementById("myInputId").focus(); 

Não ressuscitar um zumbi ou ligar minha própria diretiva (ok, é exatamente isso que eu estou fazendo):

https://github.com/hiebj/ng-focus-if

http://plnkr.co/edit/MJS3zRk079Mu72o5A9l6?p=preview

  (function() { 'use strict'; angular .module('focus-if', []) .directive('focusIf', focusIf); function focusIf($timeout) { function link($scope, $element, $attrs) { var dom = $element[0]; if ($attrs.focusIf) { $scope.$watch($attrs.focusIf, focus); } else { focus(true); } function focus(condition) { if (condition) { $timeout(function() { dom.focus(); }, $scope.$eval($attrs.focusDelay) || 0); } } } return { restrict: 'A', link: link }; } })(); 

Primeiro, uma maneira oficial de focar é o roteiro 1.1 . Enquanto isso, você pode escrever uma diretiva para implementar o foco de configuração.

Em segundo lugar, definir o foco em um item depois que ele se tornar visível no momento requer uma solução alternativa. Basta atrasar sua chamada para o elemento focus () com um $timeout .

Como o mesmo problema controlador-modifica-DOM existe para foco, desfoque e seleção, proponho ter uma diretiva ng-target :

   

Segmento angular aqui: http://goo.gl/ipsx4 e mais detalhes em blog: http://goo.gl/4rdZa

A diretiva a seguir criará uma function .focus() dentro do seu controlador, conforme especificado pelo seu atributo ng-target . (Ele cria um .blur() e um .select() também.) Demo: http://jsfiddle.net/bseib/WUcQX/

Eu achei útil usar uma expressão geral. Desta forma, você pode fazer coisas como mover automaticamente o foco quando o texto de input é válido

  

Ou focar automaticamente quando o usuário conclui um campo de tamanho fixo

  

E, claro, foco após carga

  

O código da diretiva:

 .directive('mooFocusExpression', function ($timeout) { return { restrict: 'A', link: { post: function postLink(scope, element, attrs) { scope.$watch(attrs.mooFocusExpression, function (value) { if (attrs.mooFocusExpression) { if (scope.$eval(attrs.mooFocusExpression)) { $timeout(function () { element[0].focus(); }, 100); //need some delay to work with ng-disabled } } }); } } }; }); 

Se você quisesse apenas um foco simples que fosse controlado por um ng-click.

Html:

   

Directiva:

 'use strict' angular.module('focus',['ng']) .directive('utFocus',function($timeout){ return { link:function(scope,elem,attr){ var focusTarget = attr['utFocus']; scope.$watch(focusTarget,function(value){ $timeout(function(){ elem[0].focus(); }); }); } } }); 

Um simples que funciona bem com modais:

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

Exemplo

  

Você poderia simplesmente criar uma diretiva que force o foco no elemento decorado no postLinking:

 angular.module('directives') .directive('autoFocus', function() { return { restrict: 'AC', link: function(_scope, _element) { _element[0].focus(); } }; }); 

Então no seu html:

    

Isso funcionaria para os elementos modals e ng-if toggled, não para ng-show, pois o postLinking ocorre apenas no processamento HTML.

Marcos e Blesh têm ótimas respostas; no entanto, Mark tem uma falha que Blesh aponta (além de ser complexo de implementar), e eu sinto que a resposta de Blesh tem um erro semântico na criação de um serviço que é especificamente sobre o envio de solicitação de foco para o frontend quando realmente tudo o que ele precisava era uma maneira de atrasar o evento até que todas as diretivas estivessem ouvindo.

Então, aqui está o que acabei fazendo, o que rouba muito da resposta de Blesh, mas mantém a semântica do evento controlador e o serviço “after load” separados.

Isso permite que o evento do controlador seja facilmente enganchado para outras coisas além de apenas focalizar um elemento específico e também permitir incorrer na sobrecarga da funcionalidade “após carga” somente se for necessário, o que pode não ser em muitos casos.

Uso

  
 app.controller('MyCtrl', function($scope, afterLoad) { function notifyControllerEvent() { $scope.$broadcast('controllerEvent'); } afterLoad(notifyControllerEvent); }); 

Fonte

 app.directive('focusOn', function() { return function(scope, elem, attr) { scope.$on(attr.focusOn, function(e, name) { elem[0].focus(); }); }; }); app.factory('afterLoad', function ($rootScope, $timeout) { return function(func) { $timeout(func); } }); 

Isso também é possível usar ngModelController . Trabalhando com 1.6+ (não sei com versões mais antigas).

HTML

 

JS

 $scope.myForm.myText.$$element.focus(); 

NB: Dependendo do contexto, talvez você precise include uma function de tempo limite.

NB²: Ao usar controllerAs , isso é quase o mesmo. Apenas substitua name="myForm" por name="vm.myForm" e, em JS, vm.myForm.myText.$$element.focus(); .

Provavelmente, a solução mais simples na era do ES6.

Adicionar a seguinte diretiva de um liner torna o atributo HTML ‘autofocus’ eficaz em Angular.js.

 .directive('autofocus', ($timeout) => ({link: (_, e) => $timeout(() => e[0].focus())})) 

Agora, você pode usar a syntax de autofoco HTML5 como:

  

Apenas um novato aqui, mas eu era capaz de fazê-lo funcionar em um ui.bootstrap.modal com esta diretiva:

 directives.directive('focus', function($timeout) { return { link : function(scope, element) { scope.$watch('idToFocus', function(value) { if (value === element[0].id) { $timeout(function() { element[0].focus(); }); } }); } }; }); 

e no método $ modal.open usei o seguinte para indicar o elemento onde o foco deve ser colocado:

 var d = $modal.open({ controller : function($scope, $modalInstance) { ... $scope.idToFocus = "cancelaAteste"; } ... }); 

no template eu tenho isso:

  

Eu edito a diretiva focusMe ‘Mark Rajcok’ para trabalhar com foco múltiplo em um elemento.

HTML:

  

no AngularJs Controller:

 $scope.myInputFocus= true; 

Diretiva AngulaJS:

 app.directive('focusMe', function ($timeout, $parse) { return { link: function (scope, element, attrs) { var model = $parse(attrs.focusMe); scope.$watch(model, function (value) { if (value === true) { $timeout(function () { scope.$apply(model.assign(scope, false)); element[0].focus(); }, 30); } }); } }; }); 

A seguinte diretiva fez o truque para mim. Use o mesmo atributo html autofocus para input.

 .directive('autofocus', [function () { return { require : 'ngModel', restrict: 'A', link: function (scope, element, attrs) { element.focus(); } }; }]) 

Se você estiver usando o modalInstance e tiver o object, poderá usar “then” para executar ações depois de abrir o modal. Se você não estiver usando o modalInstance, e codificado para abrir o modal, você pode usar o evento. O $ timeout não é uma boa solução.

Você pode fazer (Bootstrap3):

 $("#" + modalId).on("shown.bs.modal", function() { angular.element("[name='name']").focus(); }); 

No modalInstance você pode olhar para a biblioteca como executar o código após o modal aberto.

Não use $ timeout como este, o $ timeout pode ser 0, 1, 10, 30, 50, 200 ou mais, isso dependerá do computador cliente e do processo para abrir o modal.

Não use $ timeout, deixe o método te dizer quando você pode se concentrar;)

Espero que isso ajude! 🙂

Toda a resposta anterior não funciona se o elemento de foco desejado for injetado em um modelo de diretiva. A seguinte diretiva ajusta-se ao elemento simples ou elemento injetado diretivo (eu o escrevi no texto datilografado ). aceita o seletor para o elemento focalizável interno. se você só precisa focar o elemento self – não envie nenhum parâmetro seletor para a diretiva:

 module APP.Directives { export class FocusOnLoadDirective implements ng.IDirective { priority = 0; restrict = 'A'; constructor(private $interval:any, private $timeout:any) { } link = (scope:ng.IScope, element:JQuery, attrs:any) => { var _self = this; var intervalId:number = 0; var clearInterval = function () { if (intervalId != 0) { _self.$interval.cancel(intervalId); intervalId = 0; } }; _self.$timeout(function(){ intervalId = _self.$interval(function () { let focusableElement = null; if (attrs.focusOnLoad != '') { focusableElement = element.find(attrs.focusOnLoad); } else { focusableElement = element; } console.debug('focusOnLoad directive: trying to focus'); focusableElement.focus(); if (document.activeElement === focusableElement[0]) { clearInterval(); } }, 100); scope.$on('$destroy', function () { // Make sure that the interval is destroyed too clearInterval(); }); }); }; public static factory = ():ng.IDirectiveFactory => { let directive = ($interval:any, $timeout:any) => new FocusOnLoadDirective($interval, $timeout); directive.$inject = ['$interval', '$timeout']; return directive; }; } angular.module('common').directive('focusOnLoad', FocusOnLoadDirective.factory()); 

}

exemplo de uso para elemento simples:

  

exemplo de uso para elemento interno (geralmente para elemento injetado dynamic como diretiva com modelo):

  

você pode usar qualquer seletor de jQuery em vez de “input”

É fácil .. tente isso

html

  

javascript

 document.getElementById("ddl00").focus(); 

Eu quero contribuir para essa discussão depois de procurar por uma solução melhor e não encontrá-la, tendo que criá-la.

Critérios: 1. A solução deve ser independente do escopo do controlador pai para aumentar a reutilização. 2. Evite o uso de $ watch para monitorar alguma condição, isso é lento, aumenta o tamanho do loop digest e dificulta os testes. 3. Evite $ timeout ou $ scope $ apply () para acionar um loop digest. 4. Um elemento de input está presente no elemento em que a directiva é utilizada em aberto.

Essa é a solução que eu mais gostei:

Directiva:

 .directive('focusInput', [ function () { return { scope: {}, restrict: 'A', compile: function(elem, attr) { elem.bind('click', function() { elem.find('input').focus(); }); } }; }]); 

Html:

  

Espero que isso ajude alguém lá fora!

I think the directive is unnecessary. Use HTML id and class attributes to select the required element and have the service use document.getElementById or document.querySelector to apply focus (or jQuery equivalents).

Markup is standard HTML/angular directives with added id/classs for selection

  

Controller broadcasts event

 $scope.$emit('ui:focus', '#myInput'); 

In UI service uses querySelector – if there are multiple matches (say due to class) it will only return the first

 $rootScope.$on('ui:focus', function($event, selector){ var elem = document.querySelector(selector); if (elem) { elem.focus(); } }); 

You may want to use $timeout() to force a digest cycle

Just throwing in some coffee.

 app.directive 'ngAltFocus', -> restrict: 'A' scope: ngAltFocus: '=' link: (scope, el, attrs) -> scope.$watch 'ngAltFocus', (nv) -> el[0].focus() if nv 

Not sure if relying on the timeout is a good idea, but this works for ng-repeat because this code runs AFTER angularjs updates the DOM, so you make sure all objects are there:

 myApp.directive('onLastRepeat', [function () { return function (scope, element, attrs) { if (scope.$last) setTimeout(function () { scope.$emit('onRepeatLast', element, attrs); }, 1); }; }]); //controller for grid myApp.controller('SimpleController', ['$scope', '$timeout', '$http', function ($scope, $timeout, $http) { var newItemRemoved = false; var requiredAlert = false; //this event fires up when angular updates the dom for the last item //it's observed, so here, we stop the progress bar $scope.$on('onRepeatLast', function (scope, element, attrs) { //$scope.complete(); console.log('done done!'); $("#txtFirstName").focus(); }); }]);