$watch callback execution order - angularjs

Here's what I have:
$scope.$watch('someVar', function (val) {
$scope.someList.push(val);
execLongFunction.then(function (data) {
val.computedValue = data;
})
})
What I want: to add a value to the array in my scope, then perform some calculations and then (maybe) update the scope. The main point is that I want val to be pushed to scope array before execLongFunction is done.
The problem is: all $watch callback code is executed, kind of, immediately: even if I add some console.log() I see all of them at the same time.
What am I doing wrong?

$scope.$watch('someVar', function (newVal,oldVal) {
if(newVal != oldVal){
$scope.someList.push(newVal);
execLongFunction.then(function (data) {
val.computedValue = data;
})
}
});
$watch returns both oldValue and newValue of the $scope variable you are watching. Try to use those and execute your functions accordingly. This should work if the value of 'someVar' doesn't change every time when updated from the directive.

Related

$watch is only working without a watchExpression

In my $onInit I have a $watch:
public $onInit() {
const name = this.$state.current.name;
console.log(name);
this.$scope.$watch(name, (oldVal, newVal) => {
console.log(oldVal, newVal);
console.log(this.$state.current.name;
});
}
When I run this code the oldVal, newVal are both undefined and the this.$state.current.name value is the same in the scope of the $watch and outside of it.
When I run the code without the name expression:
public $onInit() {
const name = this.$state.current.name;
console.log(name);
this.$scope.$watch((oldVal, newVal) => {
console.log(oldVal, newVal);
console.log(this.$state.current.name);
});
}
The $watch constantly runs. The oldVal is Scope {$id: 353, .... etc but the newVal is undefined. But the this.$state.current.name is now updated inside the $watch scope.
So the value of this.$scope.current.name does change. But when I use that as the expression in the $watch the oldVal, newVal are both undefined and the this.$state.current.name isn't changed inside the $watch.
It looks like I'm doing something wrong in the watchExpression. Any suggestions?
This is expected behaviour $watch function.
From docs
$watch(watchExpression, listener, [objectEquality]);
// watchExpression - can be `expression/function`
// listener - callback
// objectEquality - deepWatch
$watch function evaluate its expression wrt $scope on each digest cycle run. So in the first approach you're just passing name as string, so what happens is, when digest cycle kicks in, it tries to evaluate name variable, which obviously not exist in $scope, that's the reason you're getting undefined value for each $watch evaluation.
Whereas in 2nd approach you're passing function/callback to $watch function. which evaluates on each digest cycle run, which is wrong. What you supposed to do is, pass callback function that will return this.$state.current.name, then only you will get desired results in $watch listener function.
It can be solved using below
this.$scope.$watch(() => {
return this.$state.current.name
}, (oldVal, newVal) => {
console.log(oldVal, newVal);
console.log(this.$state.current.name;
});
The watchExpression should be set as a string (even if it is already a string)
Did you try this ?
var name = 'some value'
this.$scope.$watch('name', (oldVal, newVal) => {
console.log(oldVal, newVal);
console.log(this.$state.current.name);
});
Documentation

$watch is not trigger without a complete refresh

I have a $watch setup to monitor an array (menuItems) of values in a service (MenuFilter).
$scope.filterMenuItems = MenuFilter.menuItems;
$scope.$watch(function () {
return MenuFilter.menuItems;
}, function (newVal, oldVal) {
if ( newVal !== oldVal ) {
$scope.filterMenuItems = newVal;
}
});
At a certain moment a menu item is deleted and I expect the list to be updated by the watch but it isn't. It is only updated when I refresh the page.
Make sure to add true as the third parameter to make a deep watch or better use $watchCollection.
The issue is that $watch only uses reference comparison, i.e. it does not monitor items inside your array - only the array reference itself (oldArray === newArray).

Watching service object for changes is not working when using the controller as syntax

I tried to follow along with this example but my code never enters the callback with the newValue when the input changes for some reason. The only difference in my example and the example bin below is that I'm using a service to hold the value instead of a controller variable.
I've been trying to make it work but I can't wrap my head around it. What am I missing?
http://jsbin.com/yinadoce/1/edit?html,js,output
Note that I've excluded the input and the steps prior to the value being updated in the service as this works perfect. The issue is only that my watch doesn't understand when the value has changed in the service.
EDIT: Forgot to inject $scope in the controller when I pasted over the code, now it's complete.
Controller:
coForms.controller('CoFormsCtrl', ['$scope', 'coFormsInfo', function($scope, coFormsInfo) {
$scope.$watch(angular.bind(coFormsInfo.getInfo(), function() {
return coFormsInfo.getInfo();
}), function(newVal) {
console.log(newVal);
});
}]);
Service:
coForms.service('coFormsInfo', [function() {
var info = {
filteredList: []
}
this.setFilteredList = function(list) {
info.filteredList = list;
};
this.getInfo = function() {
return info;
};
}]);
The watcher is there to detect any changes in the variable you're watching. How can he watch something that is not... Strictly present like a return value?
I'm not sure about what I'm saying because I'm new to angular, but the logic seems false there. You need to watch something declared to detect some changes.
You should call your service to get your infos when you need them and watch for an info variable.
EDIT
My bad there is something like that but you should declare it in a function maybe like the example on the documentation
var food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
scope.$watch(
// This function returns the value being watched. It is called for each turn of the $digest loop
function() { return food; },
// This is the change listener, called when the value returned from the above function changes
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
// Only increment the counter if the value changed
scope.foodCounter = scope.foodCounter + 1;
}
}
);

Angular service watch creates $digest loop

I am trying to get a scope to update when the return value of a service function changes. From reading, it sounds like I'm supposed to use $watch, but following the angular docs creates an infinite $digest loop in my application, here the controller:
.controller('navCtrl', function(CookieHandler, $scope){
// this works, but doesn't update when CookieHandler.get() changes
// $scope.user = CookieHandler.get();
$scope.$watch(
function() {return CookieHandler.get()},
function(newValue, oldValue) {
if( newValue !== oldValue) {
$scope.user = newValue
}
}
)
//etc
From the error message there's something about using $watch on functions that return an array, but CookieHandler.get() returns an object?
I looked around some more and read that I should use $apply instead of $watch here because it's easier to test, so I tried this:
.controller('navCtrl', function(CookieHandler, $scope){
// this works, but doesn't update when CookieHandler.get() changes
// $scope.user = CookieHandler.get();
$scope.$apply(function(scope){
scope.user = CookieHandler.get();
})
})
But this throws an error about the digest cycle already running. Is there a way to fix my $watch to stop the infinite loop? CookieHandler.get just checks the value with $cookieStore
$watch performs an equal comparison between the current value and the value from the previous digest cycle. If they are different then the function is executed.
If the object returned by CookieHandler.get() is always the same reference then the watch function will never get called. Even if you update properties of that object.
Arrays are a bad idea because they are often always a new object and JavaScript will see them as never equal.
You could serialize the object/array to JSON and use that value, but this won't work if there are changes in key/value orders.
It looks like you are trying to monitor when the current user is changed. Assuming you have a unique identifier for a user, then you might want to try something like this.
$scope.$watch(
function() {
var user = CookieHandler.get();
return (typeof user === 'undefined') ? 0 : user.id;
},
function(newValue, oldValue) {
if( newValue !== oldValue) {
$scope.user = CookieHandler.get();
}
}
);
Note that I call .get() twice. I don't know how your service works. Maybe that's not possible, but the point is to watch a value that is easily comparable.

Angularjs adding $scope.$watch after $routeProvider's resolve

I'm having an issue where I am trying to watch a certain element from my scope after the resolve is done. For some reason, it gets run when i run the second line from this snippet and I dont seem to be able to add this $watch during the "resolve".
I understand that promises are asynch, but how can I know when my resolve is done to then add the watch?
The variableToWatch can be changed either by code or in the UI (otherwise i would've just used ng-change="doWork()")
$scope.variableToWatch = $route.current.locals.data.initialValue;
$scope.listOfDependantData = $route.current.locals.data.items;
$scope.$watch('variableToWatch', function (newValue) {
myService.getNewDependantData(newValue).$promise.then(
function (items) {
$scope.listOfDependantData = items;
}
);
};
Update:
If you want to run your code only when it changes after the initial setting of value, you can use the second form of the watchFunc passed as the second argument to $scope.$watch:
$scope.$watch('variableToWatch', function (newValue, oldValue) {
if (typeof newValue !== 'undefined' && newValue !== oldValue) {
// Use the new value here ...
}
});
I don't fully follow the question, but I suspect that you want to watch the value $route.current.locals.data.initialValue instead of $scope.varibleToWatch?
In that case, you can use the alternate form of $scope.$watch with a function as the first argument:
$scope.$watch(function () { return $route.current.local.data.intialValue; }, function (newValue) {
// ...
};
Or, if you want to watch the variable on your $scope which is referenced to by the sting contained in $route.current.local.data.initialValue, then you can use this:
$scope.$watch(function () { return $scope[$route.current.local.data.intialValue]; }, function (newValue) {
// ...
};
Does this solve your problem?

Resources