Установить фокус элемента под углом

113

После поиска примеров того, как установить элементы фокуса с помощью angular, я увидел, что большинство из них используют какую-то переменную для отслеживания, а затем устанавливают фокус, и большинство из них используют одну другую переменную для каждого поля, в котором они хотят установить фокус. В форме с большим количеством полей это подразумевает множество различных переменных.

Имея в виду jquery, но желая сделать это с помощью angular, я сделал решение, в котором мы устанавливаем фокус в любой функции, используя идентификатор элемента, поэтому, поскольку я очень новичок в angular, я хотел бы получить некоторые мнения, если это правильный способ, есть проблемы, что угодно, все, что могло бы помочь мне сделать это лучше в angular.

По сути, я создаю директиву, которая отслеживает значение области, определенное пользователем с помощью директивы, или focusElement по умолчанию, и когда это значение совпадает с идентификатором элемента, этот элемент сам устанавливает фокус.

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

Вход, который по какой-то причине должен получить фокус, будет делать это

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

Здесь любой элемент для установки фокуса:

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

И пример функции, которая устанавливает фокус:

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

Это хорошее решение для angular? Есть ли у него проблемы, которых я пока не вижу?

Тьяго Сортеа де Конто
источник

Ответы:

173

Проблема с вашим решением заключается в том, что оно не работает, когда оно привязано к другим директивам, которые создают новую область видимости, например ng-repeat. Лучшим решением было бы просто создать служебную функцию, которая позволяет вам императивно фокусировать элементы в ваших контроллерах или декларативно фокусировать элементы в html.

ДЕМО

JAVASCRIPT

обслуживание

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

директива

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

контроллер

.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>
Ryeballar
источник
Мне очень нравится это решение. Однако можете ли вы объяснить причину использования $ timeout немного подробнее? Причина, по которой вы его использовали, из-за "Angular Thing" или "DOM Thing"?
user1821052
Он гарантирует, что он запускается после любых циклов дайджеста, которые выполняет angular, но это исключает циклы дайджеста, которые затрагиваются после асинхронного действия, которое выполняется после тайм-аута.
ryeballar
3
Спасибо! Для тех, кто задается вопросом, где на это ссылаются в документации по angular, вот ссылка (мне потребовалась вечность, чтобы найти)
user1821052
@ryeballar, спасибо !. Хорошее простое решение. Просто вопрос. Могу ли я использовать фабрику, созданную с помощью атрибута, вместо ожидания какого-либо события?
Pratik Gaikwad
4
В angular требуется безумный объем работы, чтобы сфокусировать ввод.
Бруно Сантос
19

Что касается этого решения, мы могли бы просто создать директиву и прикрепить ее к элементу DOM, который должен получить фокус при выполнении заданного условия. Следуя этому подходу, мы избегаем привязки контроллера к идентификаторам элементов DOM.

Пример директивы кода:

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

Возможное использование

.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
источник
1
что произойдет, если для myConditionпеременной $ scope уже установлено значение true, а затем пользователь решит сфокусироваться на другом элементе, можете ли вы все равно повторно запустить фокус, когда myConditionуже установлено значение true, ваш код отслеживает изменения для атрибута, focusOnConditionно не срабатывает, когда значение, которое вы пытаетесь изменить, остается прежним.
ryeballar
Я собираюсь обновить образец, в нашем случае у нас есть два переключателя, и мы переключаем флаг на true или false в зависимости от значения, вы можете просто изменить флаг myCondition на true или false
Браулио,
Похоже на общее решение. Лучше, чем в зависимости от идентификаторов. Мне это нравится.
Mortb
Если кто-то еще попробует это сделать, и это не сработает, мне пришлось изменить element.focus (); к элементу [0] .focus ();
Адриан Карр,
1
Это решение является гораздо более «угловатым», чем взлом на основе идентификатора, описанный выше.
setec
11

Мне нравится избегать поиска DOM, наблюдения и глобальных эмиттеров, когда это возможно, поэтому я использую более прямой подход. Используйте директиву, чтобы назначить простую функцию, которая фокусируется на элементе директивы. Затем вызовите эту функцию везде, где это необходимо в рамках контроллера.

Вот упрощенный способ прикрепления его к области видимости. См. Полный фрагмент для обработки синтаксиса Controller-as.

Директива:

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

и в html:

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

или в контроллере:

$scope.focusOnSaveInput();

Отредактировано, чтобы предоставить более подробное объяснение причины такого подхода и расширить фрагмент кода для использования в качестве контроллера.

Cstricklan
источник
Это очень хорошо и хорошо работает для меня. Но теперь у меня есть набор входов ng-repeat, которые я использую , и я хочу установить функцию фокуса только для первого. Любая идея, как я могу условно установить функцию фокуса, например, на <input>основе $index?
Гаррет Уилсон
Рад, что это полезно. Мой angular 1 немного ржавый, но вы должны иметь возможность добавить еще один атрибут к входу, например assign-focus-function-if="{{$index===0}}", а затем в качестве первой строки директивы выйти раньше, прежде чем назначать функцию, если это не так: if (attr.assignFocusFunctionIf===false) return; обратите внимание, я проверяю, если это явно, falseа не просто ложь, поэтому директива все равно будет работать, если этот атрибут не определен.
cstricklan
Контроллер-as намного проще с lodash. _.set(scope, attributes.focusOnSaveInput, function() { element.focus(); }).
Атомоск
9

Можешь попробовать

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

например, для

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

он работает для меня.

Anoop
источник
4
Примечание. Это будет работать только при использовании полного jQuery, а не при использовании jqLite, встроенного в Angular. См. Docs.angularjs.org/api/ng/function/angular.element
Джон Рикс,
4
Это способ jQuery, а не метод angular. Вопрос конкретно спрашивает, как это сделать угловатым способом.
Forgivenson
4

Другой вариант - использовать встроенную в Angular архитектуру pub-sub, чтобы уведомить вашу директиву о необходимости сосредоточиться. Подобно другим подходам, но тогда он не привязан напрямую к свойству, а вместо этого прослушивает его область действия для определенного ключа.

Директива:

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

контроллер:

//Assume this is within your controller
//And you've hit the point where you want to focus the input:
$scope.$broadcast("focusTextInput");
Mattygabe
источник
3

Я предпочел использовать выражение. Это позволяет мне делать такие вещи, как фокусировка на кнопке, когда поле допустимо, достигает определенной длины и, конечно же, после загрузки.

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

В сложной форме это также снижает потребность в создании дополнительных переменных области видимости для фокусировки.

См. Https://stackoverflow.com/a/29963695/937997

Уинри
источник