From the hitch hiker's guide to directive, light bulb example,
scope.$watch(function() {
scope.bulb = controller.getState();
});
First parameter of $watch is a function, what is exactly being watched here?
I read another SO's post on scope, this is the explanation.
"The first parameter of the $watch method (the "watchExpression") can be either an Angular string expression (that is evaluated against the $scope), or a function, which is called with $scope as the first parameter."
I am still not clear about the use of function as a first parameter to $watch, from light bulb example
- is scope implicitly passed as a parameter to that function?
- does that function implicitly return scope.bulb, so scope.bulb is being watched?
No, the function is being watched. This means the function is called and its value checked against the value it returned last time at least once every apply-digest cycle! The scope is indeed passed, but not used in your example. HTH
P.S. It is a bit odd to use a watch expression to set a value on the scope. What the watch expression function should do is return the state and set the scope value in the callback. That means it is only set when it changes rather than every time it is checked. Odd example!
To me, this looks like a wrong usage of $watch. The function should return a value, which would be watched for changes, but in this case, it will always be undefined, so a change of the value will never be watched.
A better approach would be something like this:
scope.$watch(function() {
return controller.getState();
}, function(newVal) {
scope.bulb = newVal;
});
This would watch for changes of controller.getState(), and would assign the new value to scope.bulb whenever it changes.
This is an idiom to be notified whenever $digest is called. From the documentation, see the doc for $watch:
If you want to be notified whenever $digest is called, you can register a watchExpression function with no listener. (Since watchExpression can execute multiple times per $digest cycle when a change is detected, be prepared for multiple calls to your listener.)
Related
MY ISSUE
I've been learning the basics of AJAX from two different internet sources. In the multi-step process of sending an async HTTP request, there's one small inconsistency in how the .onload property is called on the XHR request object and then set to 1) an anonymous function or 2) a callback (??? that's what I think MDN says).
1ST APPROACH
the .onload property is called on the ourRequest object and this is set to an anonymous function:
ourRequest.onload = function() {
// the code goes here
}
2ND APPROACH
the .onload property is called on the asyncRequestObject object and this is set to the name of the function (a callback??):
function handleSuccess () {
// the code goes here
}
asyncRequestObject.onload = handleSuccess;
MY QUESTION(S)
What's the difference between how the 1st and the 2nd approach work?
And then, is there any reason to use the 1st approach over the 2nd approach?
A function declaration creates a (hoisted) variable in the current scope, that has the same name as the function, and whose value is a reference to that function.
A function expression simply evaluates as a reference to that function.
So the primary difference is that where you use handleSuccess, you can continue to reference the function for other purposes elsewhere.
What's the difference between how the 1st and the 2nd approach work?
The difference is that the first function is an anonymous function expression while the second is a function with a name. Both are event handlers for the "load" event of the XMLHttpRequest .
And then, is there any reason to use the 1st approach over the 2nd approach?
If you don't plan to reuse your handler somewhere else then you don't need to declare your function with a name.
It is no different than any other programming practice. Use a variable/constant when a value is called multiple times, otherwise use a literal.
I have a directive which takes a parameter which can either be a string or an expression which resolves into a string. How can I determine whether it's one or the other?
// directive code around these lines...
$scope.$watch(attr.param, function() {
var evaluatedExpr = $parse(attr.param)(scope);
doSomethingWith(evaluatedExpr);
});
// for a string, I just call
doSomethingWith(attr.param);
If you want to eval a string as an expression use the $scope.$eval method. But, as #ChadRobinson pointed out, this shouldnt be something you need to do in your directive as the expr should probably be evaluated on the parent scope.
As a note to that, unless you are inheriting the parent scope in your directive your expr may not have the information it needs to actually evaluate from within your directive.
$scope.test = function(v){
var expr = $scope.$eval(v);
if(!expr){
console.log("could not eval, maybe a string... who knows?");
}else{
console.log(expr);
}
}
How have you defined this parameter in your scope block? Your choice here determines the answer:
# - This is a one-way binding, so it must be an expression.
& - This binds to a method that executes in the parent' scope, so you would be defining a 'getter' in the parent that the child can call. This may be useful if you are writing a generic library and you want the person using your directive to be able to choose what gets passed. In that case, you would expect that caller to perform any $parse operations required.
= - This makes a two-way binding, which means it must be a property name. It cannot be an expression (or method).
This feeds into your own code example. You are setting a watcher on the parameter. That seems to imply that you're using two-way binding - it wouldn't make sense for a one-way binding because that's only evaluated once. But in that case, you wouldn't call $parse because you'd have the raw value to work with, not an expression.
I don't know the best way to explain this in words so here is an example of my question:
http://jsfiddle.net/efZyt/
(iframe source is here: http://jsfiddle.net/H6rLQ/ )
Click the 'Change Source' button.
Type something into the input.
Repeat this a few times.
Click on the Repeat Text button.
You will get an alert that will read back to you the text that you typed into the box each step of the way.
I'm a bit confused how the callback function
function(){ alert($('#getSomeText').val()); }
gets loaded into the callback array prior to the text value existing, the function gets called after the value no longer exists (or rather, exists somewhere I can't find) and yet it is able to produce all the values.
I can't figure out where the values are being held for the callback to access them. Does the whole instance of the iframe get preserved as a closure context somewhere for the callback to run in or something?
The is the beauty of JavaScript. The value is not being store anywhere as you would think!
This is the magic of 'closures'.
Closure is basically the concept of a variable being alive even after it's scope has ended.
For example:
function outerFunction() {
innerVar = function innerFunction() {
alert('hello');
}
return innerVar;
}
var outterVar = outerFunction();
outterVar();
The above will output 'hello'. Note, the outerFunction has finished executing and the scope of 'innerVar' is also over; however, interestingly we can still execute a function defined in the outer function.
Similarly, when you pass a function to parent.register, you are NOT passing the actual value obtained by 'val()'; you are passing a function which will later be executed and internally will act as a a closure.
When it does get executed, it then takes the value by using 'val()' of the element present inside that 'closure' function.
I noticed that methods get called automatically whenever the underlying $scope variable changes.
$scope.getLength = function() {
return $scope.length;
}
My html looks like the following
<div class="test">{{getLength}}</div>
Whenever I change $scope.length, the method updates the value on the UI. I know that variables get updated because of the MVVM binding in Angular. WHy does a method get called?
It is a binding is this case too; just a binding to a function. AngularJS updates all bound elements whenever it goes through a digest cycle.
Angular does invoke those bound function everytime the scope is getting changed, no matter if this $scope.length changed or any other scope variable.
But be careful with binding too much functions into scope like this... console.log something in the function and you will see.
working on enterprise angularjs app, binding functions within big scopes can cost like 5 % of cpu power invoking bound functions containing big calculations.
Okay so what I'm trying to do is when a button is clicked function triggers that creates the following timeout:
setTimeout("if (document.getElementById(lightnum).style.backgroundColor=='green'){document.getElementById(lightnum).dataset.dead=1;document.getElementById(lightnum).style.backgroundColor='red';}", 3000);
The problem I'm having is that because the variable lightnum is reused instantly it makes it so when this timeout triggers it references the current value of lightnum not the value of lightnum when the settimeout was created. The functionality I'm looking for here is when the setTimeout triggers after 3 seconds it references the value of lightnum when it was originally created.
http://jsfiddle.net/657q2/1/
First of all, that code should be in a proper function instead of a string.
Regarding your problem, it's fixed like this:
var callback = (function(target) {
return function() {
if (target.style.backgroundColor == 'green') {
target.dataset.dead = 1;
target.style.backgroundColor = 'red';
}
};
})(document.getElementById(lightnum));
setTimeout(callback, 3000);
The problem with your original code is that lightnum in your original callback evaluates to whatever its value is when the callback is invoked, as you have already seen. What you want instead is to somehow "freeze" it to its initial value.
One attempt to do that would be to make a local copy inside the function that sets the timeout (var copy = lightnum; etc). However, this will still not work because this time when the callback is invoked it will operate on the last value that copy had the last time this function was called (possibly, but not necessarily, the same behavior as your original code).
What you really want to do is put the current value of lightnum somewhere that is only accessible by the code of the callback; in JS, the only way to do that is pass it to a function as an argument. This necessitates the funky "function that returns function" syntax above: the inner function is the actual desired callback and the outer function is a "firewall" that prevents any outside meddling with the variable in question.
Use a closure instead of a string:
setTimeout(
(function(ln) {
return function() {
if (document.getElementById(ln).style.backgroundColor=='green') {
document.getElementById(ln).dataset.dead=1;
document.getElementById(ln).style.backgroundColor='red';
}
};
}(lightnum)),
3000
);
"Jon" and "Ted Hopp" have proper answers, but I might just add why functions are better here:
As Barmar stated, a string evaluation will be in global scope.
IDEs will not syntax highlight within strings, so it makes your code less readable to yourself and others.
It is slower to evaluate.
Most seriously, if a portion of your string is from untrusted input, it could cause a security problem (e.g., if part of the string is coming from your database of user comments, a malicious user could add code there to grab the user's cookies).