AngularJS - how to focus on an element via a controller - angularjs

I know the question has been asked multiple time, but I can't seem to find anywhere how to focus to an element from within a controller. What is the best approach? Would it be better to do a directive? But if so, then how would I call it within my controller? Or is it better to create a service then?
What I already have and works properly from within HTML code is a directive:
.directive('ngxFocus', ['$timeout', function($timeout) {
return function(scope, element, attr) {
$timeout(function () {
element.focus();
}, 10);
};
}])
Can I call directive within controller? I'm still learning AngularJS and I'm a bit confused on what the best approach is in this case. I really want to do it via the controller, at the moment I use a simple 1 line of jQuery to focus, but yeah it's not the Angular way and so I'd like to go with the correct way.
Note
To be more specific with an example, let say I have 10 inputs in the HTML and let say that inside the execution of a function (defined in the controller), I want to focus on 1 of the multiple inputs directly from the function (again this is all declared inside the controller). I would rather not write anything inside the HTML code, if possible, but instead call a focus function or something that will focus to the input I chose. I know I could write it simply in jQuery with $('input12').focus(); but I want to know how to do it the AngularJS way. All the answers I get so far are based on writing a Directive, that also equals to writing something inside the HTML, isn't there any other way???
Example
For more explicit example of my form... I have a first input connected to a Yahoo web service (stock market), this input filled by the user will hold a stock quotes symbol that can be anywhere in the world and then the user will choose (from a dropdown) his Bank account... now from there, my controller will check that the stock quotes market is in the same currency as the user's bank account (ex.: GOOG is US currency, if user's account is in $CAD, it will fail because GOOG is in $USD). If currency isn't the same, I want to advise my user and to do so I would seriously prefer to focus on the field so he could change his symbol if he made an error.

I you're trying to work with elements in controller, be sure you're going wrong, the controller's target in to bind data received from services to view, not to manipulate view.
If you want to focus on an element with route change:
app.directive('focuser', ['$location', function ($location) {
return {
restrict: 'A',
link: function ($scope, $element) {
$scope.$watch(function () {
//simply focus
$element.focus();
//or more specific
if ($location.$$url == '/specific/path') {
$element.focus();
}
});
}
};
}]);

I've made this directive:
app.directive('rfocus',function(){
return {
restrict: 'A',
controller: function($scope, $element, $attrs){
var fooName = 'setFocus' + $attrs.rfocus;
$scope[fooName] = function(){
$element.focus();
}
},
}
});
It adds to controller's $scope function to set focus on element. Name of the function is based on value given in attribute.
Using: <input type="text" rfocus="Input1"/> will create function setFocusInput1() which you can use in your controller.
Here is the fiddle: http://jsfiddle.net/aartek/2PJMQ/

I've recently started to learn Angular, too, but hopefully I can provide you a different way of approaching this.
I've been using some basic jQuery to focus, too, so in that regard, I can't really help you. However, with regard to calling a directive within a controller, I can't find any articles that say "yes, you can", or "no, you can't". I know that you can declare a controller within a directive, though, so you miiiiight be able to do something like this:
.directive('ngxFocus', function() {
return {
restrict: 'A',
controller: //do your controller stuff here
}])

I know it's an old question, but here's an other approach, where you set to true a variable in the controller, and that's this action that set the focus to your element.
Try this:
myAngularModule.directive('goFocus', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.goFocus, function (newValue) {
if (newValue) {
$timeout(function () {
element[0].focus();
}, 100);
}
});
element.bind("blur", function (e) {
$timeout(function () {
scope.$apply(attrs.goFocus + "=false");
}, 10);
});
element.bind("focus", function (e) {
$timeout(function () {
scope.$apply(attrs.goFocus + "=true");
}, 10);
});
}
}
}]);
In HTML template:
<input go-focus="focusvar1" type="text" ng-model="mytext1" />
<input go-focus="focusvar2" type="text" ng-model="mytext2" />
<input go-focus="focusvar3" type="text" ng-model="mytext3" />
<button ng-click="doFocus()">OK</button>
In javascript angular controller:
myAngularModule.controller('myController', function () {
var self = this;
self.doFocus = function () {
// do some logic and focus your field
self.focusvar2 = true;
};
});

Related

Angularjs controller function vs directive function

Lately I've been building some modules and in some of them I only used controllers (controller is set within an existing directive I already need to use to load template) to have this comunnication between services and the view, for example:
$scope.callFunction = function(data) {
factRequest = saveData(data);
};
I also noticed I could do this from within a directive, like this:
link:function(scope) {
scope.callFunction = function(data) {
factRequest.saveData(data);
}
}
//or..
link:function(scope, element, attr) {
attrValue = attr.myValue;
element.bind('click', function(attrValue) {
factRequest.saveData(attrValue);
});
}
//or even..
link:function(scope, element, attr) {
attrValue = attr.myValue;
element.bind('click', function(attrValue) {
factRequest.saveData(attrValue);
});
var elButton = element.fin('span'); //for example
elButton.bind('click', function(attrValue) {
factRequest.saveData(attrValue);
});
}
Considering a scenario where this a reusable object, for example, a product where it display on multiple pages and have a commom function, such as addFavorite, addCart, addWishList, etc.. And also considering performance.
What is the difference between those call methods? And what is the best option to use as a call Function?
To restate, you are calling a service method on a click event and want to know where the best place to put that logic is.
Let's look at each of your examples:
Controller
angular.module('myApp').controller('MyController', function($scope, factRequest) {
$scope.callFunction = function(data) {
factRequest.saveData(data);
};
});
First of all, whenever I find myself injecting $scope into a controller I question my approach. This is because adding variables to the current scope creates hidden dependencies if you are relying using those variables in a child controller -- and is unnecessary if you are not.
Instead, you should be using the controllerAs syntax and adding the function to the controller itself. Something like this:
angular.module('myApp').controller('MyController', function(factRequest) {
var vm = this;
vm.callFunction = function(data) {
factRequest.saveData(data);
};
});
...and you would access it in your template like this:
<div ng-controller="MyController as vm">
<input ng-model="vm.data">
<button ng-click="vm.callFunction(vm.data)">
Click Me!
</button>
</div>
This is a perfectly good approach utilizing native Angular directives.
Directive w/ Link Function
angular.module('myApp').directive('myDirective', function(factRequest) {
return {
link: function(scope) {
scope.callFunction = function(data) {
factRequest.saveData(data);
}
}
};
});
Again, I don't like this because you are adding the function to scope. If you have a directive and want to expose some functionality to the template, you should use a controller. For example:
angular.module('myApp').directive('myDirective', function() {
return {
controller: 'MyDirectiveController',
controllerAs: 'myDir',
template: '<input ng-model="myDir.data">' +
'<button ng-click="myDir.callFunction(myDir.data)">' +
'Click Me!' +
'</button>'
};
}).controller('MyDirectiveController', function(factRequest) {
var myDir = this;
myDir.callFunction = function(data) {
factRequest.saveData(data);
}
});
This is essentially the same as the first example, except that it is now a reusable component.
Directive w/ Click Event Handler
angular.module('myApp').directive('myDirective', function(factRequest) {
return {
link: function(scope, element, attr) {
element.on('click', function() {
factRequest.saveData(scope.$eval(attr.myValue));
});
}
};
});
Notice I took a few liberties here. For one thing, an event handler function gets the event object as its first argument, so trying to pass attr.myValue wouldn't work. Also, I call scope.$eval(), which is a best practice that enables the use of Angular expressions in the myValue attribute.
I like this approach best, because it doesn't rely on the use of other directives like ng-click. In other words, this directive is more self-contained.
One thing I should add is that Angular will not remove this event listener when the element is removed from the DOM. It is a best practice to clean up after your directive, like this:
angular.module('myApp').directive('myDirective', function(factRequest) {
return {
link: function(scope, element, attr) {
function onClick() {
factRequest.saveData(scope.$eval(attr.myValue));
}
element.on('click', onClick);
scope.$on('$destroy', function() {
element.off('click', onClick);
});
}
};
});
Conclusion
From a performance perspective, all of these approaches are roughly equivalent. The first two don't add any watchers themselves, but ng-click and ng-model do so its six of one, half a dozen of the other.

ngModel and How it is Used

I am just getting started with angular and ran into the directive below. I read a few tutorials already and am reading some now, but I really don't understand what "require: ngModel" does, mainly because I have no idea what ngModel does overall. Now, if I am not insane, it's the same directive that provides two way binding (the whole $scope.blah = "blah blah" inside ctrl, and then {{blah}} to show 'blah blah' inside an html element controlled by directive.
That doesn't help me here. Furthermore, I don't understand what "model: '#ngModel' does. #ngModel implies a variable on the parents scope, but ngModel isn't a variable there.
tl;dr:
What does "require: ngModel" do?
What does "model : '#ngModel'" do?
*auth is a service that passes profile's dateFormat property (irrelevant to q)
Thanks in advance for any help.
angular.module('app').directive('directiveDate', function($filter, auth) {
return {
require: 'ngModel',
scope: {
model : '#ngModel',
search: '=?search'
},
restrict: 'E',
replace: true,
template: '<span>{{ search }}</span>',
link: function($scope) {
$scope.set = function () {
$scope.text = $filter('date')($scope.model, auth.profile.dateFormat );
$scope.search = $scope.text;
};
$scope.$watch( function(){ return $scope.model; }, function () {
$scope.set();
}, true );
//update if locale changes
$scope.$on('$localeChangeSuccess', function () {
$scope.set();
});
}
};
});
ngModel is an Angular directive responsible for data-binding. Through its controller, ngModelController, it's possible to create directives that render and/or update the model.
Take a look at the following code. It's a very simple numeric up and down control. Its job is to render the model and update it when the user clicks on the + and - buttons.
app.directive('numberInput', function() {
return {
require: 'ngModel',
restrict: 'E',
template: '<span></span><button>+</button><button>-</button>',
link: function(scope, element, attrs, ngModelCtrl) {
var span = element.find('span'),
plusButton = element.find('button').eq(0),
minusButton = element.find('button').eq(1);
ngModelCtrl.$render = function(value) {
updateValue();
};
plusButton.on('click', function() {
ngModelCtrl.$setViewValue(ngModelCtrl.$modelValue + 1);
updateValue();
});
minusButton.on('click', function() {
ngModelCtrl.$setViewValue(ngModelCtrl.$modelValue - 1);
updateValue();
});
function updateValue(value) {
span.html(ngModelCtrl.$modelValue);
}
}
};
});
Working Plunker
Since it interacts with the model, we can use ngModelController. To do that, we use the require option to tell Angular we want it to inject that controller into the link function as its fourth argument. Now, ngModelController has a vast API and I won't get into much detail here. All we need for this example are two methods, $render and $setViewValue, and one property, $modelValue.
$render and $setViewValue are two ways of the same road. $render is called by Angular every time the model changes elsewhere so the directive can (re)render it, and $setViewValue should be called by the directive every time the user does something that should change the model's value. And $modelValue is the current value of the model. The rest of the code is pretty much self-explanatory.
Finally, ngModelController has an arguably shortcoming: it doesn't work well with "reference" types (arrays, objects, etc). So if you have a directive that binds to, say, an array, and that array later changes (for instance, an item is added), Angular won't call $render and the directive won't know it should update the model representation. The same is true if your directive adds/removes an item to/from the array and call $setViewValue: Angular won't update the model because it'll think nothing has changed (although the array's content has changed, its reference remains the same).
This should get you started. I suggest that you read the ngModelController documentation and the official guide on directives so you can understand better how this all works.
P.S: The directive you have posted above isn't using ngModelController at all, so the require: 'ngModel' line is useless. It's simply accessing the ng-model attribute to get its value.

JQuery UI Spinner is not updating ng-model in angular

Angular's ng-model is not updating when using jquery-ui spinner.
Here is the jsfiddle http://jsfiddle.net/gCzg7/1/
<div ng-app>
<div ng-controller="SpinnerCtrl">
<input type="text" id="spinner" ng-model="spinner"/><br/>
Value: {{spinner}}
</div>
</div>
<script>
$('#spinner').spinner({});
</script>
If you update the text box by typing it works fine (you can see the text change). But if you use the up or down arrows the model does not change.
Late answer, but... there's a very simple and clean "Angular way" to make sure that the spinner's spin events handle the update against ngModel without resorting to $apply (and especially without resorting to $parse or an emulation thereof).
All you need to do is define a very small directive with two traits:
The directive is placed as an attribute on the input element you want to turn into a spinner; and
The directive configures the spinner such that the spin event listener calls the ngModel controller's $setViewValue method with the spin event value.
Here's the directive in all its clear, tiny glory:
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
Note that $setViewValue is intended for exactly this situation:
This method should be called when an input directive wants to change
the view value; typically, this is done from within a DOM event
handler.
Here's a link to a working demo.
If the demo link provided above dies for some reason, here's the full example script:
(function () {
'use strict';
angular.module('ExampleApp', [])
.controller('ExampleController', ExampleController)
.directive('jqSpinner', jqSpinner);
function ExampleController() {
var c = this;
c.exampleValue = 123;
};
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
})();
And the minimal example template:
<div ng-app="ExampleApp" ng-controller="ExampleController as c">
<input jq-spinner ng-model="c.exampleValue" />
<p>{{c.exampleValue}}</p>
</div>
Your fiddle is showing something else.
Besides this: Angular can not know about any changes that occur from outside its scope without being aknowledged.
If you change a variable of the angular-scope from OUTSIDE angular, you need to call the apply()-Method to make Angular recognize those changes. Despite that implementing a spinner can be easily achieved with angular itself, in your case you must:
1. Move the spinner inside the SpinnerCtrl
2. Add the following to the SpinnerCtrl:
$('#spinner').spinner({
change: function( event, ui ) {
$scope.apply();
}
}
If you really need or want the jQuery-Plugin, then its probably best to not even have it in the controller itself, but put it inside a directive, since all DOM-Manipulation is ment to happen within directives in angular. But this is something that the AngularJS-Tutorials will also tell you.
Charminbear is right about needing $scope.$apply(). Their were several problems with this approach however. The 'change' event only fires when the spinner's focus is removed. So you have to click the spinner then click somewhere else. The 'spin' event is fired on each click. In addition, the model needs to be updated before $scope.$apply() is called.
Here is a working jsfiddle http://jsfiddle.net/3PVdE/
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
var mdlAttr = $(this).attr('ng-model').split(".");
if (mdlAttr.length > 1) {
var objAttr = mdlAttr[mdlAttr.length - 1];
var s = $scope[mdlAttr[0]];
for (var i = 0; i < mdlAttr.length - 2; i++) {
s = s[mdlAttr[i]];
}
s[objAttr] = ui.value;
} else {
$scope[mdlAttr[0]] = ui.value;
}
$scope.$apply();
}
}, 0);
});
Here's a similar question and approach https://stackoverflow.com/a/12167566/584761
as #Charminbear said angular is not aware of the change.
However the problem is not angular is not aware of a change to the model rather that it is not aware to the change of the input.
here is a directive that fixes that:
directives.directive('numeric', function() {
return function(scope, element, attrs) {
$(element).spinner({
change: function(event, ui) {
$(element).change();
}
});
};
});
by running $(element).change() you inform angular that the input has changed and then angular updates the model and rebinds.
note change runs on blur of the input this might not be what you want.
I know I'm late to the party, but I do it by updating the model with the ui.value in the spin event. Here's the updated fiddle.
function SpinnerCtrl($scope, $timeout) {
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
$scope.spinner = ui.value;
$scope.$apply();
}
}, 0);
});
}
If this method is "wrong", any suggestions would be appreciated.
Here is a solution that updates the model like coder’s solution, but it uses $parse instead of parsing the ng-model parameter itself.
app.directive('spinner', function($parse) {
return function(scope, element, attrs) {
$(element).spinner({
spin: function(event, ui) {
setTimeout(function() {
scope.$apply(function() {
scope._spinnerVal = = element.val();
$parse(attrs.ngModel + "=_spinnerVal")(scope);
delete scope._spinnerVal;
});
}, 0);
}
});
};
});

create "nested" ng-click inside of Angular Directive

I have a directive which open up a bootstrap-tours on call of t.start():
app.directive('tourGuide', function ($parse, $state) {
var directiveDefinitionObject = {
restrict: 'E',
replace: false,
link: function (scope, element, attrs) {
var t = new Tour({container: $("#main"),
backdrop: false,
debug:true
});
t.addStep({
element: "#main",
title: "Title123",
content: "Content123"
});
t.init();
t.start();
}};
return directiveDefinitionObject;
});
I want to create a button which on click could call variable t.start(). Is it even possible? I want to achieve this so could be independent of functions inside controllers, because this directive will be on every single view of the application, so it would be nice if it could call a parameter inside itself. Ive tryed to create a template in directive with a button, and add a ng-clikc action with t.start() and ofcourse it failed because variable t is not known to controller where ever my directive is.
EXAMPLE:
Lets say i have 2 views ShowItems and CreateItem they have 2 dirfferent controllers. in those views i have 1 button/link, on click of it i want to show my TourGuide. Thats simple.
Now in my TourGuide i have 2 different Steps, and when i press on a button in CreateItem view i want to see the step in Tour Guide for CreateItem view, and vise versa.
Thats simple if i use functions inside my controller. But is it possible to use directive ONLY, because i could have 20 different controllers?
Based on a few assumptions - I assume what you want here is to dynamically call a routine in scope from a directive. Take the following code as an example
HTML/View Code
<div my-directive="callbackRoutine">Click Here</div>
Controller
function MyController($scope) {
$scope.callbackRoutine = function () {
alert("callback");
};
}
Directive
app.directive("myDirective", function () {
return {
restrict: 'A',
link: function (scope, element, attr){
element.bind('click', function (){
if (typeof scope[attr.myDirective] == "function"){
scope[attr.myDirective]();
}
});
}
};
});
In this, you specify the callback routine as part of the directive. The key to the equation is that the scope for the directive inherits from any parent scope(s) which means you can call the routine even from the scope passed to the directive. To see a working example of this, see the following plunkr: http://plnkr.co/edit/lQ1QlwwWdpNvoYHlWwK8?p=preview. Hope that helps some!

How to prefill a calculation form with initial values?

I've a long calculation form and want to prefill some values for displaying initial results. These initial values should live in the template to have these easily editable. It seems, the angular way is to use a directive which reads the input value fields and initializes the app with these values.
Here is one way to to set the model value from the input field:
<input name="card[description]" value="Visa-4242" ng-model="card.description" ng-initial>
Coffeescript:
app = angular.module 'forms', []
app.directive 'ngInitial', ->
restrict: 'A'
controller: ['$scope', '$element', '$attrs', '$parse', ($scope, $element, $attrs, $parse) ->
val = $attrs.sbInitial || $attrs.value
getter = $parse($attrs.ngModel)
setter = getter.assign
setter($scope, val)
]
Source: Angular js init ng-model from default values
Unfortunately this doesn't work in my app. The values are displayed but the results aren't calculated. I have to manual fill out the fields in the browser to start the calculation.
The input fields are watched in my controller, like so:
$scope.$watch(
'inputValues.permeatCapacity',
function (newValue, oldValue) {
if (newValue === oldValue) {
return;
}
permeatCapacity = $scope.inputValues.permeatCapacity;
permeatPerDay = permeatFactory.getPermeatPerDay(permeatCapacity);
$scope.permeatPerDay = $filter('number')(permeatPerDay, 2);
}
);
What's the best directive for this problem or is there a better way in Angular.js?
UPDATE
I've just found a really dirty way in the controller to update the input fields after initialization that make use of the bound calculations. It not only feels like an bad idea, the initial values also don't live in the template:
$timeout(function () {
$scope.inputValues.overallRecovery = 40;
$scope.inputValues.permeatCapacity = 7.2;
}, 0);
In comparison, this initial object in my controller fills out the fields but doesn't trigger the watchers (important for the bound calculations):
$scope.inputValues = {
overallRecovery: 40,
permeatCapacity: 7.2
};
But there is a better way to do this, isn't it?
Ok, here's what I found out. The condition:
if (newValue === oldValue) {
return;
}
in my watch methods blocked the initial calculation. I can now use a really simple directive for my usecase:
An input field in the template:
<input type="text" id="overall-recovery" value="40" ng-model="inputValues.overallRecovery" initial-value>
The directive:
angular.module('ksbApp')
.directive('initialValue', function ($compile) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var initialValue = attrs.value;
ngModel.$setViewValue(initialValue);
}
};
});
In this case, the value "40" is pushed to the model during initialization.
Is there anything I could do better?
Not sure why you would need a directive for this, unless I dont understand the question fully. Spin up the model in scope, setting the values where on the controller, and then bind them using ngmodel.
in the controller:
scope.inputValues = {permeatCapacity: 1};
scope.calc = function(){
return permeatFactory(scope.inputValues.perMeatCapacity);
};
in the view:
<input ng-model="inputValues.permeatCapacity" type="text">
<button ng-click="calc()" />
In later versions of angular you can even have the calculation run AS the values in the model change using ng-change.

Resources