$scope.$watch with non existing variable as value - angularjs

I've been trying to understand $watch function on $scope object. This looks pretty straight forward but the thing that I don't understand is why (on page load) listener function is being executed when I pass a non existing $scope object variable in value function.
$scope.$watch ('nonExistingVariableIdentifier', function () { console.log('Variable value changed'); });
Am I missing something?

The watch runs when it is created.
The full use of a $watch is:
$scope.$watch("nonExistantVariable", function(newValue, oldValue) {
if (newValue == oldValue) {
// First run
}
else {
// After First run
}
})
This is the correct way to differentiate between the initialization and an actual change.
After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.
From the Angular JS Docs for $watch - Docs

Related

How can I get notifications of rootScope changes?

I've got a global variable in my rootScope for AngularJS which has properties updated in other various places (outside of angular). For example, lets say the property 'name' is updated on it. It seems like it updates on the root scope fine and after doing an apply or firing a controller function on any child function the view eventually updates, but this is a problem.
How can I get the controllers to update the templates to reflect the rootScope changes immediately?
How can I observe any changes whatsoever on this object, and invoke apply?
You can create a $watch as so:
// Assuming that $rootScope.name exists
$rootScope.$watch('name', function(newValue, oldValue)
{
// You have access to both the newValue
// and the oldValue
});
To $watch for objects, taking this from Angular's Site, a third boolean character is needed
objectEquality (optional) boolean: Compare object for equality rather
than for reference.
// Assuming that $rootScope.obj exists
$rootScope.$watch('obj', function(newValue, oldValue)
{
// You have access to both the newValue
// and the oldValue
}, true);
But know that it is not a good idea to use the $rootScope to store content. You should consider using a service or a factory for that purpose. Read more about this here.
Create a watch for it on rootscope ($rootScope.$watch('varName', function (newValue) {}[,true])).
The varName parameter accepts expressions with '.' for sub-objects and '[]' for array indexes. The third parameter indicates listening fire changes "inside" the object.

Watch variable and change it

In AngularJS I have a directive that watches a scope variable. When the variable contains certain data then I need to alter that variable a bit. The problem is that when I change the variable that my $watch is triggered again. So I end up in a continuous loop.
scope.$watch('someVar', function(newValue, oldValue) {
console.log(newValue);
scope.someVar = [Do something with someVar];
});
This keeps triggering $watch again, which makes sense. But I do need a way to change the watched variable. Is there a way to do this?
When a variable is watched for changes using $scope.$watch, angular checks if the reference has changed. If it has, then the $watch handler is executed to update the view.
If you plan on changing the scope variable within the $watch handler, it will trigger an infinite $digest loop because the scope variable reference changes every time that it is called.
The trick to getting around the infinite digest issue is to preserve the reference inside your $watch handler using angular.copy (docs):
scope.$watch('someVar', function(newValue, oldValue) {
console.log(newValue);
var someVar = [Do something with someVar];
// angular copy will preserve the reference of $scope.someVar
// so it will not trigger another digest
angular.copy(someVar, $scope.someVar);
});
Note: This trick only works for object references. It will not work with primitives.
In general, its not a good idea to update a $watched variable within its own $watch listener. However, sometimes it may be unavoidable.
Inside function use if condition to avoide continues for loop
scope.$watch('someVar', function(newValue, oldValue) {
if(newValue!==oldValue) {
console.log(newValue);
scope.someVar = [Do something with someVar];
}
});
This is how dirty-checking works. Every time something on the $scope changes Angular will spin through everything that is attached to the scope and it will keep doing so untill there are no more changes.
If you want to do something like that, you will have to make sure that your $watch function is idempotent. You will have to look at both newValue and oldValue and figure out wether you've already applied the changes to your variable in this $digest loop. How you can do that depends a bit on what kind of changes you're doing to someVar.
Generally speaking, changing a watched variable in a watch-function is unfortunately not a good idea.
you can manage it using a bool variable
$scope.someVarChanged = false;
scope.$watch('someVar', function(newValue, oldValue) {
console.log(newValue);
$scope.someVarChanged = !$scope.someVarChanged!;
if($scope.someVarChanged) {
scope.someVar = [Do something with someVar];
}
});
yes you can cancel it like this
var wathcer = scope.$watch('someVar', function(newValue, oldValue) {
console.log(newValue);
scope.someVar = [Do something with someVar];
});
wathcer(); // clear the watch

$watch waiting too long to recognize a change

This is some logic in a controller:
function newGame(){
$scope.gameOver = true;
$timeout(function(){
//do stuff
$scope.gameOver = false;
}, 3000);
}
In a directive I have:
scope.$watch(scope.gameOver,function(){ console.log("changed!", scope.gameOver);})
I'd like to do something based on scope.gameOver. I use the timeout function to give the game 3 seconds of time where gameOver = true. However, watch does nothing during those 3 seconds and instead fires off at the end of those 3 seconds where scope.gameOver has already been turned back into false.
What is the proper way of doing this?
Your $watch callback function will be invoked at least once when the $watch is set up, irrespective of whether or not your scope.gameOver variable changes.
This is pointed out in the official documentation:
After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher.
I think you may be running into unexpected behaviour because you are specifying to $watch a primitive value instead of a reference to the variable holding the value of interest.
In other words,
scope.$watch(scope.gameOver, function() { ... });
as you have specified, would be the same as,
scope.$watch(true, function() { ... });
which obviously will not do anything productive.
Instead, prefer specifying your $watch using a function to return a reference to scope.gameOver or alternatively take advantage of how the variable to $watch can be an Angular expression:
// Function to return reference to the variable to watch
scope.$watch(function() { return scope.gameOver; }, function() { ... });
// Expression for Angular to evaluate
scope.$watch('gameOver', function() { ... });
Hope that helps.
Watch will be triggered only when the watch parameter changes. So in your code the $scope.gameOver
changes only at the end of 3 seconds and hence the watch is triggered.

Why does the watch fire when oldvalue is equal to newvalue?

I have:
$scope.$watch('option.sSubject', function (newValue, oldValue) {
if (newValue !== oldValue) {
and:
console.log(newValue)
1
undefined
console.log(oldValue)
1
undefined
Can someone explain why the watch fires when there seems to be no change in value. Note that this is when I am just opening the page.
Quote from the API documentation of $scope.$apply():
After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.

AngularJS multiple $watch cause multiple requests on controller initialization

Consider the following code;
function ListCtrl($scope){
this.fetchResults = function(){};
// so I can pass this as a reference & spyOn fetchResults
function fetchResults = function(){return this.fetchResults()}.bind(this);
$scope.page = 1;
$scope.order = null;
$scope.$watch('page',fetchResults);
$scope.$watch('order',fetchResults);
this.fetchResults();
}
I want to write this code in this manner, so I can test if each watch triggers the fetchResults. But this way, when ListCtrl is initalized, it calls fetchResults 3 times
explicitly called
for $watch('page')
for $watch('order')
What can I do to make the ListCtrl call fetchResults only once on initialization ?
From the $scope.$watch docs:
After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.
So you can do something like this:
function watchFetchResults(newValue, oldValue) {
if(newValue !== oldValue){
fetchResults();
}
}
$scope.$watch('page',watchFetchResults);
$scope.$watch('order',watchFetchResults);
You could also do:
$scope.$watch('page + order', fetch);
This would watch both expressions at once, simply concatenating them in an angular expression.

Resources