Como definir o foco no campo de entrada?


759

Qual é a 'maneira Angular' para definir o foco no campo de entrada no AngularJS?

Requisitos mais específicos:

  1. Quando um Modal é aberto, defina o foco em um predefinido <input>dentro deste Modal.
  2. Sempre que <input>ficar visível (por exemplo, clicando em algum botão), defina o foco nele.

Tentei atingir o primeiro requisito com autofocus, mas isso funciona apenas quando o Modal é aberto pela primeira vez e apenas em alguns navegadores (por exemplo, no Firefox, ele não funciona).

Qualquer ajuda será apreciada.

Respostas:


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

Defina uma diretiva e faça com que $ watch uma propriedade / gatilho saiba para quando focar o elemento:

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

O $ timeout parece ser necessário para fornecer o tempo modal para renderização.

'2.' Sempre que <entrada> ficar visível (por exemplo, clicando em algum botão), defina o foco.

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

<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


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

Resposta original para 1., usando um escopo isolado:

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 .

Resposta original para 2., usando um escopo isolado:

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

Como precisamos redefinir a propriedade trigger / focusInput na diretiva, '=' é usado para a ligação 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.


1
Consulte também a diretiva "focus" de @ Josh: stackoverflow.com/a/14859639/215945 Ele não usou um escopo isolado em sua implementação.
Mark Rajcok

2
@ MarkRajcok apenas curioso sobre isso: esta versão funciona, mas se eu definir um ng-modelno campo de entrada, o valor do modelo será perdido quando eu usar esta diretiva com o escopo isolado. O problema não acontece se eu tentar a versão de Josh sem o escopo isolado. Ainda sou novato e gostaria de entender a diferença. Aqui está um Plunker que mostra isso.
Sunil D.

1
Eu descobri que o nº 1 funciona muito bem com o AngularJS 1.0.6. No entanto, ao executar sob um depurador, notei que toda vez que eu rejeitava e reabria meu modal, estava vendo mais uma chamada adicional para a função que define o foco do que na época anterior. Modifiquei essa função levemente para desatar o relógio quando value != "true", e isso pareceu resolver o meu problema.
Andrew Brown

1
Então ... em uma página com estado, eu tinha a mesma coisa porque $ watch é $ watch e desenvolvedores angulares adoram $ watch ... bem, eu encontrei um problema, Sr. MarkRajcok, um problema que resolvi com a solução (básica) I proposto abaixo.
precisa saber é o seguinte

3
hmm para mim o código funciona, como em eu posso vê-lo executando corretamente e o elemento [0] é o controle correto. no entanto .focus () não dispara um foco. possivelmente bootstrap ou algo mais está interferindo nisso?
Sonic Soul

264

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

Mark Rajcok é o homem ... e sua resposta é uma resposta válida, mas tem tido um defeito (desculpe Mark) ...

... Tente usar o booleano para focar a entrada, depois desfoque a entrada e tente usá-lo para focar a entrada novamente. Não funcionará, a menos que você redefina o booleano para false e, em seguida, $ digest, em seguida, redefina-o para true. Mesmo se você usar uma comparação de cadeias na sua expressão, será forçado a alterar a cadeia para outra coisa, $ digest, e depois alterá-la novamente.(Isso foi resolvido com o manipulador de eventos de desfoque.)

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

Use um evento, o recurso esquecido do Angular.

Afinal, o JavaScript adora eventos. Os eventos 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ê pode usá-lo assim:

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

e depois em qualquer lugar do 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ê pode se associar a eventos que já existem. Por outro lado, você começa a fazer algo inteligente, fazendo com que diferentes partes do seu aplicativo publiquem eventos nos quais outras partes do seu aplicativo possam se inscrever.

De qualquer forma, esse tipo de coisa grita "dirigido a eventos" para mim. Acho que, como desenvolvedores Angular, tentamos arduamente martelar pinos em forma de escopo $ em buracos em forma de evento.

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


Solução atualizada

Após o comentário de @ ShimonRachlenko abaixo, mudei 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 princípio descrito acima.

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

Uso

<input type="text" focus-on="focusMe"/>
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);
    });
  }
});

3
Você precisa encerrar a chamada $broadcastcom $timeoutse quiser que isso funcione ao entrar no controlador. Caso contrário, boa solução.
Shimon Rachlenko

@ShimonRachlenko - Obrigado. Mas não sei ao certo o que você quer dizer com $ timeout. Se eu quisesse transmitir quando o construtor do controlador estivesse sendo processado, acabaria de transmitir imediatamente. Um tempo limite não faria nada além de adiar a transmissão para uma execução posterior no loop de eventos.
Ben Lesh

1
Sim, e isso é suficiente para a diretiva inicializar. Caso contrário, o evento é transmitido antes que a diretiva comece a ouvi-lo. Novamente, isso é necessário apenas quando você deseja acionar sua diretiva ao entrar na página.
Shimon Rachlenko

2
Você está certo. Acho que não o tinha usado para me concentrar na carga. Vou atualizar a resposta com algo mais robusto.
Ben Lesh

1
Essa é, de longe, a solução mais elegante e "angular". Mesmo que eu tenha copiado o código na maioria das vezes quando me deparei com esse problema, fico feliz que você tenha criado um módulo para ele! Sinceramente, acho que vale a pena tentar colocá-lo no núcleo angular.
Randolpho

241

Eu achei algumas das outras respostas muito complicadas quando tudo o que você realmente precisa é isso

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

uso é

<input name="theInput" auto-focus>

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


1
Existem várias maneiras de fazer isso, uma maneira possível que seja realmente direta e fácil seria no escopo (controlador aqui) definir o ID do item no qual você deseja focar quando clicar no botão e, em seguida, no diretiva simplesmente ouça isso. Nesse caso, você não precisaria colocar a diretiva em nenhum lugar em particular, apenas em algum lugar nessa página (usei esse tipo de diretivas de observadores com sucesso no passado, principalmente com foco em coisas e rolagem) - Então, se você ' re usando jquery (faria isso mais simples) apenas encontrar esse elemento id e focá-la
ecancil

14
A solução com tempo limite zero não funciona para mim se a entrada estiver localizada no modo pop-up. Mas mesmo 10 ms corrigir o problema
Eugene Fidelin

@ancancil: Gosto da sua abordagem porque é mais simples, mas você precisa definir o tempo limite para ~ 500ms no IE, porque a animação da aparência modal resulta em um cursor piscando fora da entrada. Eu não sei de uma maneira legal de fazer isso acontecer quando a animação terminar, então eu a forço com os 500ms.
Dev93

não vai funcionar, se você tem que puxar mais dados do banco de dados, é preciso esperar para o controlador completar os dados puxando, então adicionar tempo suficiente no lugar de 0
Ali Adravi

1
Para pessoas como a @Ade que querem usar isso de uma maneira simples ng-click: digamos que clicar em um botão tenha ng-click="showInput = !showInputsua entrada. Em seguida, na sua entrada real, adicione ng-if="showInput". Se você pressionar o botão, a diretiva será executada novamente a cada vez. Eu estava tendo um problema com isso, pois estava usando ng-showa abordagem errada.
JVG 29/02

91

HTML tem um atributo autofocus.

<input type="text" name="fname" autofocus>

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


29
Infelizmente, isso só funciona uma vez, se for o caso. Usei isso em uma situação de edição angular no local e funcionou muito bem na primeira vez no Chrome, e não no Firefox. No Chrome, quando clico em outro item para editar no local ou mesmo no mesmo item novamente, ele não ganha mais foco. Os documentos afirmam que ele funciona no carregamento da página, o que acontece apenas uma vez em um aplicativo Angular. Se fosse assim tão simples, estaríamos todos sentados na praia, ganhando 20%!
Nocturno

62

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

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


2
Sem o jquery carregado: angular.forEach (document.querySelectorAll ('. Selector')), função (elem) {elem.focus ();});
precisa saber é o seguinte

29
Não é uma prática ruim colocar isso em um controlador?
VitalyB

2
Se colocar essa linha jqlite dentro de um controlador for uma prática ruim, não seria melhor colocar essa linha jqlite dentro de uma diretiva?
esportes

3
Looking up elements via selectors is not supported by jqLite!
Entendi

1
@VitalyB Eu tive um caso em que eu precisava desfocar um elemento após uma chamada ajax. Eu poderia adicionar apenas uma linha de JS puro no retorno de chamada dentro do controlador ou criar uma diretiva inteira exclusivamente para o desfoque dessa entrada. Eu apenas senti que era um aborrecimento demais, então eu fui para a primeira opção.
Sebastianb

55

Isso funciona bem e uma maneira angular de focar o controle de entrada

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

Embora essa não seja uma maneira angular pura de executar a tarefa, a sintaxe segue o estilo angular. O Jquery desempenha um papel indiretamente e acessa diretamente o DOM usando Angular (jQLite => JQuery Light).

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


Não sei se essa é uma ótima abordagem para os aplicativos ViewModel. Em Angular, isso deve ser feito via diretivas.
Agat

Por exemplo, se alguém alterar uma caixa de texto A e você precisar exibir uma janela pop-up e definir o foco em outra caixa de texto B.
Sanjeev Singh

1
Estou recebendo este erro ao usar este:Looking up elements via selectors is not supported by jqLite!
JVG

6
Tanto quanto posso dizer, você precisará carregar o jQuery como uma dependência primeiro, caso em que angular.elementse torna um invólucro $() / jQuery(). Assim, sem que isto não vai funcionar, e você está basicamente apenas usando jQuery qualquer maneira (mas me corrija se eu estiver errado)
JVG

@ Jascination: jqLite é desenvolvido para remover a dependência do jQuery. Você não precisa de jQuery inteiro para acessar um elemento no DOM. Mas se você tiver o jQuery instalado, o Angular fará referência ao jQuery. Verifique isto: docs.angularjs.org/api/angular.element
Sanjeev Singh

31

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 interna, extraída das profundezas obscuras 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();
            }
        }
    }
});
<input value="hello" />
<!-- this input automatically gets focus on creation -->
<input value="world" auto-focus />

Documentos da Diretiva AngularJS completa: https://docs.angularjs.org/api/ng/service/$compile


2
Tive que quebrar o elemento [0] .focus () em um tempo limite de $ para fazê-lo funcionar para mim.
Codinome

@bbodenmiller Meu modal tem um efeito fade-out aplicado; quando o elemento é construído, ele é invisível (100% transparente), então o navegador bloqueia o foco da chamada silenciosamente. Aparentemente, alguns milissegundos passados ​​permitem focar na entrada / botão quase transparente, mas visível.
8685 Snowindy

1
de acordo com os documentos, as funções "link" são "postLink" por padrão. veja também: bennadel.com/blog/…
jody tate

17

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:

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

O que faz:

Ele focaliza a entrada à medida que se torna visível com o ng-show. Não é possível usar $ watch ou $ aqui.


Na verdade, acredito que {{isVisible}} esteja criando um relógio de qualquer maneira, portanto a instrução "Não é possível usar $ watch" está incorreta.
Masjul

Sim, acho que você está certo. Mas ainda serve como uma maneira fácil de fazer isso.
Edhowler

17

Eu escrevi uma diretiva de foco de ligação bidirecional, assim como o modelo recentemente.

Você pode usar a diretiva focus como esta:

<input focus="someFocusVariable">

Se você criar alguma variável de escopoFocusVariable true em qualquer lugar do seu controlador, a entrada será focada. E se você deseja "desfocar" sua entrada, é possível definir alguma variável de foco como false. É como a primeira resposta de Mark Rajcok, mas com ligação de mão dupla.

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:

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

Aqui está o violino:

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


Essa deve ser a resposta correta. Abrange todos os casos de uso.
Kunal

11

Para quem usa Angular com o plug-in Bootstrap:

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

Você pode se conectar à openedpromessa da instância modal:

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

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

Boa! No entanto, no meu caso, $timeoutcom 50msestá em necessidade, em vez de 0.
Lk_vc

8

Achei útil usar uma expressão geral. Dessa forma, você pode fazer coisas como mover automaticamente o foco quando o texto de entrada é válido

<button type="button" moo-focus-expression="form.phone.$valid">

Ou focalize automaticamente quando o usuário preencher um campo de comprimento fixo

<button type="submit" moo-focus-expression="smsconfirm.length == 6">

E, claro, foco após carregamento

<input type="text" moo-focus-expression="true">

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

7

Para não ressuscitar um zumbi ou conectar minha própria diretiva (ok, é exatamente isso que estou fazendo):

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

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

<input focus-if />

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

Agora estou usando essa diretiva e ela funciona muito bem, resolve o problema geral e não é muito complexa. Desisti de procurar uma solução sem tempo limite de $. Continue encontrando comentários dizendo que o foco está "no roteiro" para Angular 1.1 e 1.2, mas estou usando o 2.xe ainda sem gerenciamento de foco. Heh.
tekHedd

6

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

Segundo, para definir o foco em um item depois que ele se tornou visível atualmente, é necessária uma solução alternativa. Apenas adie sua chamada para o elemento focus () com a $timeout.

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

<input type="text" x-ng-model="form.color" x-ng-target="form.colorTarget">
<button class="btn" x-ng-click="form.colorTarget.focus()">do focus</button>

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

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


+1 para a referência do roteiro. Na verdade, eu acabei de vê-lo na documentação da versão 1.2 ainda considerada instável ( docs.angularjs.org/api/ng.directive:ngFocus )
Edwin Dalorzo

27
@EdwinDalorzo ngFocusparece ser uma maneira de lidar com focuseventos, não uma maneira de definir o foco em um elemento.
Teleclimber # 26/13

6

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

Aqui está um exemplo.

No arquivo html:

<input type="text" id="myInputId" />

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

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

9
Essa não é uma "maneira angular" e não aconselha as pessoas a tocarem no DOM em seus controladores.
Smajl

É melhor não usar baunilha no Angular, porque o Angular não pode rastreá-lo nem mantê-lo sincronizado com todo o resto.
Edgar Quintero

4

Se você queria apenas um foco simples, controlado por um clique-ng.

Html:

<input ut-focus="focusTigger">

<button ng-click="focusTrigger=!focusTrigger" ng-init="focusTrigger=false"></button>

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

4

Simples que funciona bem com os modais:

.directive('focusMeNow', ['$timeout', function ($timeout)
{
    return {
        restrict: 'A',

        link: function (scope, element, attrs)
        {


            $timeout(function ()
            {
                element[0].focus();
            });



        }
    };
}])

Exemplo

<input ng-model="your.value" focus-me-now />

Perfeito para mim. Obrigado
Alexander

3

Você pode apenas 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:

<input type="text" name="first" auto-focus/> <!-- this will get the focus -->
<input type="text" name="second"/>

Isso funcionaria para elementos modais e ng-se alternados, não para ng-show, pois o postLinking acontece apenas no processamento HTML.


3

Mark e Blesh têm ótimas respostas; no entanto, Mark tem uma falha que Blesh aponta (além de ser complexa 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 front-end quando realmente tudo o que ele precisava era uma maneira de adie o evento até que todas as diretivas estejam ouvindo.

Então, eis o que acabei fazendo, que rouba muito a resposta de Blesh, mas mantém a semântica do evento do controlador e o serviço "pós-carregamento" separados.

Isso permite que o evento do controlador seja facilmente conectado a outras coisas que não sejam apenas o foco em um elemento específico e também permite incorrer na sobrecarga da funcionalidade "pós-carregamento" apenas se for necessário, o que pode não acontecer em muitos casos.

Uso

<input type="text" focus-on="controllerEvent"/>
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);
  }
});

A primeira e única resposta que eu (um iniciante completo) pude entender. Em vez de "afterLoad (notifyControllerEvent);", eu costumava "notifyControllerEvent ()". Caso contrário, terminou com alguns erros.
precisa saber é o seguinte

3

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

HTML

<form name="myForm">
    <input type="text" name="myText" ng-model="myText">
</form>

JS

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

-

NB: Dependendo do contexto, talvez você precise quebrar uma função 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();,.


3

Provavelmente, a solução mais simples para a idade do ES6.

A adição da seguinte diretiva de liner torna o atributo 'autofoco' do HTML eficaz no Angular.js.

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

Agora, você pode usar a sintaxe de foco automático HTML5 como:

<input type="text" autofocus>

@Stephane sim, sim, então torna-se.directive('autofocus', ['$timeout', ($timeout) => ({link: (_, e) => $timeout(() => e[0].focus())})])
Michael Plautz

2

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 em que o foco deve ser colocado:

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

no modelo eu tenho isso:

<input id="myInputId" focus />

2

A diretiva a seguir fez o truque para mim. Use o mesmo atributo html de foco automático para entrada.

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

1
você receberá um erro ao executar este a menos que jQuery está incluído, deve antes ser elemento [0] .focus ()
Ryan M

2

Se você estiver usando modalInstance e tiver o objeto, poderá usar "then" para executar ações após abrir o modal. Se você não estiver usando o modalInstance e for codificado para abrir o modal, poderá usar o evento. O tempo limite de $ não é uma boa solução.

Você pode fazer (Bootstrap3):

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

Em modalInstance, você pode ver na biblioteca como executar o código após o modal aberto.

Não use $ timeout assim, o $ timeout pode ser 0, 1, 10, 30, 50, 200 ou mais, isso dependerá do computador cliente e do processo de abertura modal.

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

Espero que isso ajude! :)


2

Toda a resposta anterior não funciona se o elemento de foco desejado for injetado em um modelo de diretiva. A diretiva a seguir se encaixa no elemento simples ou no elemento injetado de diretiva (escrevi em texto datilografado ). aceita 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:

<button tabindex="0" focus-on-load />

exemplo de uso para elemento interno (geralmente para elemento injetado dinâmico como diretiva com modelo):

<my-directive focus-on-load="input" />

você pode usar qualquer seletor jQuery em vez de "input"


2

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

HTML:

<input  focus-me="myInputFocus"  type="text">

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

1

Quero contribuir para esta discussão depois de procurar 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 o teste. 3. Evite $ timeout ou $ scope. $ Apply () para acionar um loop de digestão. 4. Um elemento de entrada está presente no elemento em que a diretiva é usada em aberto.

Esta é 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:

 <div focus-input>
     <input/>
 </div>

Espero que isso ajude alguém por aí!


Você acabou de recriar o que o html "label" faz, basta substituir "div focus-input" por "label" e se livrar da diretiva.
frst

1

É fácil .. tente isso

html

<select id="ddl00">  
 <option>"test 01"</option>  
</select>

javascript

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

O correto é fora de contexto, mas não da maneira AngularJS de fazer as coisas
codemonkey

1

Se você deseja focar em um elemento específico, pode usar a abordagem abaixo.

  1. Crie um serviço chamado focus.

    angular.module('application')
    .factory('focus', function ($timeout, $window) {
        return function (id) {
            $timeout(function () {
                var element = $window.document.getElementById(id);
                if (element)
                    element.focus();
            });
        };
    });
  2. Injete-o no controlador de onde você deseja ligar.

  3. Ligue para este serviço.


0

Penso que a directiva é desnecessária. Use os atributos de ID e classe HTML para selecionar o elemento necessário e faça com que o serviço use document.getElementById ou document.querySelector para aplicar o foco (ou equivalentes a jQuery).

A marcação é diretivas HTML / angulares padrão com ID / classes adicionadas para seleção

<input id="myInput" type="text" ng-model="myInputModel" />

Evento de transmissões do controlador

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

Na interface do usuário, o serviço usa querySelector - se houver várias correspondências (por exemplo, devido à classe), ele retornará apenas a primeira

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

Você pode usar $ timeout () para forçar um ciclo de resumo


0

Apenas jogando um pouco de café.

app.directive 'ngAltFocus', ->
    restrict: 'A'
    scope: ngAltFocus: '='
    link: (scope, el, attrs) ->
        scope.$watch 'ngAltFocus', (nv) -> el[0].focus() if nv
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.