¿Cómo poner el foco en el campo de entrada?

Resuelto Misha Moroshko asked hace 11 años • 33 respuestas

¿Cuál es la 'forma angular' de establecer el foco en el campo de entrada en AngularJS?

Requisitos más específicos:

  1. Cuando se abre un modal , establezca el foco en un predefinido <input>dentro de este modal.
  2. Cada vez <input>que sea visible (por ejemplo, al hacer clic en algún botón), establezca el foco en él.

Intenté cumplir el primer requisito con autofocus, pero esto funciona sólo cuando se abre Modal por primera vez, y sólo en ciertos navegadores (por ejemplo, en Firefox no funciona).

Misha Moroshko avatar Feb 12 '13 20:02 Misha Moroshko
Aceptado
  1. Cuando se abre un modal, establezca el foco en una <entrada> predefinida dentro de este modal.

Defina una directiva y haga que $watch una propiedad/disparador para que sepa cuándo enfocar el 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));
            });
        }
    };
}]);

plomero

El $timeout parece ser necesario para darle tiempo al modal para renderizarse.

'2.' Cada vez que <input> se vuelve visible (por ejemplo, al hacer clic en algún botón), establezca el foco en él.

Cree una directiva esencialmente como la anterior. Observe alguna propiedad de alcance y, cuando se convierta en verdadera (configúrela en su controlador ng-click), ejecútela element[0].focus(). Dependiendo de su caso de uso, es posible que necesite o no un $timeout 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;
          //});
        }
      });
    }
  };
});

plomero


Actualización 7/2013 : He visto a algunas personas usar mis directivas de alcance aislado originales y luego tener problemas con los campos de entrada incrustados (es decir, un campo de entrada en el modal). Una directiva sin un nuevo alcance (o posiblemente un nuevo alcance secundario) debería aliviar algo del dolor. Entonces, arriba actualicé la respuesta para no usar ámbitos aislados. A continuación se muestra la respuesta original:

Respuesta original para 1., utilizando un alcance aislado:

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 .

Respuesta original para 2., utilizando un alcance aislado:

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

Dado que necesitamos restablecer la propiedad trigger/focusInput en la directiva, '=' se usa para el enlace de datos bidireccional. En la primera directiva, '@' era suficiente. También tenga en cuenta que cuando usamos '@' comparamos el valor de activación con "verdadero", ya que @ siempre da como resultado una cadena.

Mark Rajcok avatar Feb 12 '2013 16:02 Mark Rajcok

##(EDITAR: agregué una solución actualizada debajo de esta explicación)

Mark Rajcok es el hombre... y su respuesta es una respuesta válida, pero ha tenido un defecto (lo siento Mark)...

... Intente usar el valor booleano para enfocarse en la entrada, luego difumine la entrada y luego intente usarlo para enfocar la entrada nuevamente. No funcionará a menos que restablezca el valor booleano a falso, luego $digest y luego lo restablezca a verdadero. Incluso si usa una comparación de cadenas en su expresión, se verá obligado a cambiar la cadena por otra cosa, $digest, y luego volver a cambiarla. (Esto se ha solucionado con el controlador de eventos de desenfoque).

Entonces propongo esta solución alternativa:

Utilice un evento, la característica olvidada de Angular.

Después de todo, a JavaScript le encantan los eventos. Los eventos están inherentemente débilmente acoplados y, mejor aún, evitas agregar otro $watch a tu $digest.

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

Entonces ahora podrías usarlo así:

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

y luego en cualquier lugar de tu aplicación...

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

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

Esto es fantástico porque puedes hacer todo tipo de cosas con algo como esto. Por un lado, podría vincularse a eventos que ya existen. Por otra parte, empiezas a hacer algo inteligente al hacer que diferentes partes de tu aplicación publiquen eventos a los que otras partes de tu aplicación pueden suscribirse.

De todos modos, este tipo de cosas me grita "impulsado por eventos". Creo que, como desarrolladores de Angular, nos esforzamos mucho en introducir clavijas con forma de $scope en agujeros con forma de evento.

¿Es la mejor solución? No sé. Es una solución.


Solución actualizada

Después del comentario de @ShimonRachlenko a continuación, cambié ligeramente mi método para hacer esto. Ahora uso una combinación de un servicio y una directiva que maneja un evento "detrás de escena":

Aparte de eso, es el mismo principio descrito anteriormente.

Aquí hay una demostración rápida de Plunk.

###Uso

<input type="text" focus-on="focusMe"/>
app.controller('MyCtrl', function($scope, focus) {
    focus('focusMe');
});

###Fuente

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);
    });
  }
});
Ben Lesh avatar Aug 18 '2013 03:08 Ben Lesh

He descubierto que algunas de las otras respuestas son demasiado complicadas cuando todo lo que realmente necesitas es esto.

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

el uso es

<input name="theInput" auto-focus>

Usamos el tiempo de espera para permitir que las cosas en el dom se procesen, aunque sea cero, al menos espera eso; de esa manera esto funciona en modales y demás también.

ecancil avatar Jan 01 '2014 02:01 ecancil

HTML tiene un atributo autofocus.

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

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

Rayron Victor avatar Sep 29 '2015 12:09 Rayron Victor

También puede utilizar la funcionalidad jqlite integrada en angular.

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

JordanC avatar Dec 11 '2013 08:12 JordanC