AngularJS $watch is failing for arrays after 1st update - angularjs

I need to track when a model value which is an array has been updated by a directive but $watch appears to be failing. I created a fiddle to illustrate the problem:
http://jsfiddle.net/DsTLN/
I setup a watch so that I can perform actions when the model changes.
$scope.$watch('model.title', function(newValue, oldValue) {
if ($scope.model) {
console.debug("watchedTitle=" + newValue);
$scope.watchedTitle = newValue ;
}});
If the model newValue is an integer, $watch fires consistently as shown in the debug logs:
watchedTitle=1
watchedTitle=2
watchedTitle=3
watchedTitle=4
watchedTitle=5
I setup a 2nd watch to monitor changes to the array:
$scope.$watch('model.titlearray', function(newValue, oldValue) {
If the newValue is an array $watch only fires on the 1st usage:
watchedTitlearray: 1
and after that my 2nd watch doesn't fire.
Any idea what I'm doing wrong?

In order to watch the change of elements in a list, set the 3rd parameter of $watch to true
$scope.$watch('model.titlearray', function(newValue, oldValue) {
// ...
}, true);

In Angular 1.2 you should use:
$scope.$watchCollection('model.titlearray', function(newValue, oldValue) {
// ...
});
http://docs.angularjs.org/api/ng.$rootScope.Scope

As sza said, you need to set the 3rd parameter of $watch to true. This is so that you can compare the two objects for equality rather than by reference, as stated in the angular scope docs
$watch(watchExpression, listener, objectEquality)
objectEquality (optional) - Boolean - Compare object for equality rather than for reference.
I fixed your jsfiddle so that it works the way you intended to, here is the link: http://jsfiddle.net/r4zce/

Related

Angular JS prevent $watch listener

I've use dropdown list which render some results via $scope.$watch listener callback in Angular JS controller.
When i change dropdown list other value it works fine.
But in some case i need to keep last selected results(does not need rise that event).
I've tried ng-change and ng-click but it works after
$scope.$watch raised.
Any help would be appreciated
There is an overload on $scope.$watch() where you can also capture the previous value of the watched property.
$scope.$watch(propertyExpression, newValue, oldValue)
Is this what you are looking for? It is hard to read your mind without code samples ;)
If you want to keep even older values, you might want to consider keeping some state in your controller.
BTW, it can be interesting to always use this overload and check first whether the object you are watching really changed using (newValue !== oldValue) because (if I recall correctly) $scope.$watch is also triggered when $scope.$apply is called on a higher level node.
See if this works,
var unregisterWatch =
$scope.$watch('param', function(newVal, oldVal) {
if (newVal === oldVal)
return; //
});
call this function when required, it will cancel Angular's watch on the variable.

$scope.$watch with non existing variable as value

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

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

Model update detection

I try to implement an autosaving feature for my forms basing on the $watch function of AngularJS. I initialized watching as described below:
var unbindWatcher = $scope.$watch('selectedElement', function(newValue, oldValue) {
(...)
});
The selected element is loaded using the $resource object and its query method. The selectedElement is set then using one element of the list. Fields of the selectedElement are linked with form elements using ng-model attribute.
The problem is that the callback specified in the $watch method is called once before any update is done.
Is it a normal behavior of the $watch method? How to catch when the selectedElement is actually updated?
Thanks for your help.
Thierry
Yes it is expected behavior After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. You can add below line to function
var unbindWatcher = $scope.$watch('selectedElement', function(newValue, oldValue) {
if(newvalue===oldvalue) return;
});

Resources