Model update detection - angularjs

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

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

Accessing ngModel from controller and watch for changes

i have make custom directive which is working good. It's plunker is at http://plnkr.co/edit/GxM78QRwSjTrsX1SCxF7?p=preview
in that directive there is ngModel call deptStation I want access it in controller so that i can use it as parameter in other function to make new array. I also want to watch it also so on every change I can call the function.
<plexus-select items="deptStations" header-text="Select station" text="Select departure..." text-icon="ion-chatbubble-working" text-field="City_Name_EN" text-field2="City_Code" value-field="City_Code" ng-model="deptStation">
</plexus-select>
I tried to write below code but it don't show console log
$scope.$watch('deptStation', function(newValue, oldValue) {
if(oldValue != newValue) {
// perform something
console.log('New Value ' + newValue);
}
I'm not exactly sure about ionic directives, but your issue is probably because one or more of the ionic directives creates a new scope, so just doing ng-model="deptStation" creates a new property in that scope rather than your controller's.
To avoid these issues you should make sure not to bind to primitives, but arrays/objects instead. You should create the property like so (renamed to selectedStation for clarity):
$scope.selectedStation = {value: null};
Then your watch will work:
$scope.$watch('selectedStation.value', function (station) {
console.log('watch', station);
});
http://plnkr.co/edit/96VgPzEXZuzxmHt32Afy?p=preview
As #m59 said though, it seems you'd be better just using two-way binding than ng-model.

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.

AngularJS $watch is failing for arrays after 1st update

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/

Resources