Définir la mise au point de l'élément de manière angulaire

113

Après avoir cherché des exemples de la façon dont les éléments de mise au point sont définis avec angulaire, j'ai vu que la plupart d'entre eux utilisent une variable pour surveiller la mise au point, et la plupart d'entre eux utilisent une variable différente pour chaque champ qu'ils souhaitent définir. Dans un formulaire, avec beaucoup de champs, cela implique beaucoup de variables différentes.

Avec jquery way à l'esprit, mais voulant le faire de manière angulaire, j'ai fait une solution selon laquelle nous nous concentrons sur n'importe quelle fonction en utilisant l'id de l'élément, donc, comme je suis très nouveau dans angular, j'aimerais avoir des opinions si cette façon est juste, avoir des problèmes, peu importe, tout ce qui pourrait m'aider à le faire de la meilleure façon en angulaire.

Fondamentalement, je crée une directive qui surveille une valeur de portée définie par l'utilisateur avec la directive, ou le focusElement de la valeur par défaut, et lorsque cette valeur est la même que l'ID de l'élément, cet élément se concentre lui-même.

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

Une entrée qui a besoin de se concentrer pour une raison quelconque fera de cette façon

<input my-focus id="input1" type="text" />

Voici tout élément à définir:

<a href="" ng-click="clickButton()" >Set focus</a>

Et l'exemple de fonction qui définit le focus:

$scope.clickButton = function() {
    $scope.focusElement = "input1";
}

Est-ce une bonne solution en angulaire? Y a-t-il des problèmes que je ne vois pas encore avec ma mauvaise expérience?

Tiago Zortéa De Conto
la source

Réponses:

173

Le problème avec votre solution est qu'elle ne fonctionne pas bien lorsqu'elle est liée à d'autres directives qui créent une nouvelle portée, par exemple ng-repeat. Une meilleure solution serait de créer simplement une fonction de service qui vous permet de focaliser impérativement des éléments dans vos contrôleurs ou de focaliser des éléments de manière déclarative dans le html.

DEMO

JAVASCRIPT

Un service

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

Directif

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

Manette

.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>
seigle
la source
J'aime vraiment cette solution. Pouvez-vous expliquer, cependant, la raison d'utiliser un peu plus $ timeout? Est-ce que la raison pour laquelle vous l'avez utilisé est due à une "chose angulaire" ou une "chose DOM"?
user1821052
Il s'assure qu'il s'exécute après tous les cycles de résumé que fait angular, mais cela exclut les cycles de résumé qui sont affectés après une action asynchrone qui est exécutée après l'expiration du délai.
ryeballar
3
Merci! Pour ceux qui se demandent où cela est référencé dans la documentation angulaire, voici le lien (m'a pris une éternité à trouver)
user1821052
@ryeballar, merci !. Belle solution simple. Juste une question cependant. Puis-je utiliser la fabrique créée via l'attribut au lieu d'attendre qu'un événement se produise?
Pratik Gaikwad
4
C'est insensé la quantité de travail nécessaire en angulaire juste pour concentrer une entrée.
Bruno Santos
19

À propos de cette solution, nous pourrions simplement créer une directive et l'attacher à l'élément DOM qui doit obtenir le focus lorsqu'une condition donnée est satisfaite. En suivant cette approche, nous évitons de coupler le contrôleur aux ID d'élément DOM.

Exemple de directive de code:

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

Un usage possible

.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">
Braulio
la source
1
que se passera-t-il lorsque la myConditionvariable $ scope a déjà été définie sur true et que l'utilisateur choisit de se concentrer sur un autre élément, pouvez-vous toujours redéclencher le focus quand myConditionest déjà vrai, votre code surveille les modifications de l'attribut focusOnConditionmais il ne se déclenchera pas lorsque la valeur que vous essayez de modifier est toujours la même.
ryeballar
Je vais mettre à jour l'exemple, dans notre cas, nous avons deux boutons radio, et nous basculons le drapeau sur vrai ou faux en fonction de la valeur, vous pouvez simplement changer le drapeau myCondition en vrai ou faux
Braulio
Cela ressemble à une solution générique. Mieux que dépendre des identifiants. Je l'aime.
mortb
Au cas où quelqu'un d'autre essaye cela et que cela ne fonctionne pas, j'ai dû changer element.focus (); à l'élément [0] .focus ();
Adrian Carr
1
Cette solution est beaucoup plus `` angulaire '' que le piratage basé sur l'id ci-dessus.
setec le
11

J'aime éviter les recherches DOM, les montres et les émetteurs globaux autant que possible, j'utilise donc une approche plus directe. Utilisez une directive pour attribuer une fonction simple qui se concentre sur l'élément directive. Appelez ensuite cette fonction partout où cela est nécessaire dans le cadre du contrôleur.

Voici une approche simplifiée pour l'attacher à la portée. Consultez l'extrait de code complet pour gérer la syntaxe en tant que contrôleur.

Directif:

app.directive('inputFocusFunction', function () {
    'use strict';
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            scope[attr.inputFocusFunction] = function () {
                element[0].focus();
            };
        }
    };
});

et en html:

<input input-focus-function="focusOnSaveInput" ng-model="saveName">
<button ng-click="focusOnSaveInput()">Focus</button>

ou dans le contrôleur:

$scope.focusOnSaveInput();

Modifié pour fournir plus d'explications sur la raison de cette approche et pour étendre l'extrait de code pour une utilisation en tant que contrôleur.

cstricklan
la source
C'est très agréable et cela fonctionne bien pour moi. Mais maintenant, j'ai un ensemble d'entrées en utilisant ng-repeat, et je veux uniquement définir la fonction de mise au point pour la première. Une idée de la façon dont je pourrais définir conditionnellement une fonction de mise au point <input>basée sur, $indexpar exemple?
Garret Wilson
Heureux que ce soit utile. Mon angular 1 est un peu rouillé, mais vous devriez pouvoir ajouter un autre attribut à l'entrée, comme assign-focus-function-if="{{$index===0}}", puis en tant que première ligne de la directive, quittez tôt avant d'attribuer une fonction si ce n'est pas vrai: if (attr.assignFocusFunctionIf===false) return; Notez que je vérifie si c'est explicitement falseet pas seulement faux, donc la directive fonctionnera toujours si cet attribut n'est pas défini.
cstricklan
Controller-as est beaucoup plus simple avec lodash. _.set(scope, attributes.focusOnSaveInput, function() { element.focus(); }).
Atomosk
9

Tu peux essayer

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

par exemple.

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

ça marche pour moi.

Anoop
la source
4
Remarque: cela ne fonctionnera que si vous utilisez jQuery complet plutôt que de compter sur jqLite intégré dans Angular. Voir docs.angularjs.org/api/ng/function/angular.element
John Rix
4
C'est la manière jQuery de faire cela, pas une manière angulaire. La question demande spécifiquement comment le faire de manière angulaire.
pardonner le
4

Une autre option serait d'utiliser l'architecture pub-sub intégrée d'Angular afin de notifier votre directive de focus. Similaire aux autres approches, mais il n'est alors pas directement lié à une propriété et écoute plutôt sa portée pour une clé particulière.

Directif:

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

Manette:

//Assume this is within your controller
//And you've hit the point where you want to focus the input:
$scope.$broadcast("focusTextInput");
Mattygabe
la source
3

J'ai préféré utiliser une expression. Cela me permet de faire des choses comme me concentrer sur un bouton lorsqu'un champ est valide, atteint une certaine longueur, et bien sûr après le chargement.

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

Sur une forme complexe, cela réduit également le besoin de créer des variables de portée supplémentaires à des fins de concentration.

Voir https://stackoverflow.com/a/29963695/937997

winry
la source