以角度方式设置单元焦点

在寻找如何设置焦点的角度元素的例子后,我看到他们中的大多数使用一些变量来观察然后设置焦点,他们中的大多数使用一个不同的变量为每个领域,他们想要设置焦点。在一个有很多字段的表单中,这意味着有很多不同的变量。

考虑到 jquery 的方式,但是想要以角度的方式来做,我做了一个解决方案,我们在任何函数中使用元素的 id 来设置焦点,所以,由于我在角度方面是很新的,我想要得到一些意见,如果这种方式是正确的,有问题,无论如何,任何可以帮助我在角度方面做得更好的东西。

基本上,我创建了一个指令来监视用户使用指令定义的范围值,或者默认的 focus Element,当该值与元素的 id 相同时,该元素自己设置焦点。

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

这是一个很好的角度解决方案吗? 它是否有问题,以我贫穷的经验,我还没有看到呢?

243296 次浏览

The problem with your solution is that it does not work well when tied down to other directives that creates a new scope, e.g. ng-repeat. A better solution would be to simply create a service function that enables you to focus elements imperatively within your controllers or to focus elements declaratively in the html.

DEMO

JAVASCRIPT

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

Directive

  .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

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

About this solution, we could just create a directive and attach it to the DOM element that has to get the focus when a given condition is satisfied. By following this approach we avoid coupling controller to DOM element ID's.

Sample code directive:

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

A possible usage

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

I prefered to use an expression. This lets me do stuff like focus on a button when a field is valid, reaches a certain length, and of course after load.

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

On a complex form this also reduces need to create additional scope variables for the purposes of focusing.

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

I like to avoid DOM lookups, watches, and global emitters whenever possible, so I use a more direct approach. Use a directive to assign a simple function that focuses on the directive element. Then call that function wherever needed within the scope of the controller.

Here's a simplified approach for attaching it to scope. See the full snippet for handling controller-as syntax.

Directive:

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

and in html:

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

or in the controller:

$scope.focusOnSaveInput();

angular.module('app', [])
.directive('inputFocusFunction', function() {
'use strict';
return {
restrict: 'A',
link: function(scope, element, attr) {
// Parse the attribute to accomodate assignment to an object
var parseObj = attr.inputFocusFunction.split('.');
var attachTo = scope;
for (var i = 0; i < parseObj.length - 1; i++) {
attachTo = attachTo[parseObj[i]];
}
// assign it to a function that focuses on the decorated element
attachTo[parseObj[parseObj.length - 1]] = function() {
element[0].focus();
};
}
};
})
.controller('main', function() {});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>


<body ng-app="app" ng-controller="main as vm">
<input input-focus-function="vm.focusOnSaveInput" ng-model="saveName">
<button ng-click="vm.focusOnSaveInput()">Focus</button>
</body>

Edited to provide more explanation about the reason for this approach and to extend the code snippet for controller-as use.

You can try

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

for eg.

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

its working for me.

Another option would be to use Angular's built-in pub-sub architecture in order to notify your directive to focus. Similar to the other approaches, but it's then not directly tied to a property, and is instead listening in on it's scope for a particular key.

Directive:

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

Controller:

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