$watch is only working without a watchExpression - angularjs

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

Related

Angular $watch - execute watch block only once

I have a simple $watch on a dropdown value :
$scope.view;
$scope.$watch('view', function(){
console.log(1);
// watch block
}
The value is chaining with ng-model:
<select ng-model="view" ng-options="x for x in name">
</select>
Due to the $digest the value 1 is printed twice, is there a way to tell angular to execute the the $watch block only once?
Simple answer: unregister the watch after the first successful run:
var viewWatch = $scope.$watch('view', function () {
viewWatch(); // Stops the watch
console.log(1);
//watch block
};
However this may or may not yield the results you want, because sometime the $watch is called upon initialization. You may want to set some conditions:
var viewWatch = $scope.$watch('view', function (currentValue, oldValue) {
if (currentValue && !oldValue) {
viewWatch(); // Stops the watch
console.log(1);
//watch block
}
};
You could simply test if view is about to be set for the first time (declared) or is about to be changed (by the select) by testing the params :
$scope.$watch('view'), function(newVal, oldVal) {
if (oldVal && oldVal != newVal) {
console.log(1)
}
}

Angular: Why is $watch being called?

Any reason why a $scope.$watch would be called when the value it is watching for does not exist anywhere within the app?
As you can see below, no matter what string I assign for the watchExpression , the function runs ...
$scope.$watch( 'kwhefjkewgfweahhfwujad' , // a bunch of random letters
function()
{ console.log("it fired ... why?");
}
) ;
Firefox does not indicate any errors.
try
$scope.$watch( 'kwhefjkewgfweahhfwujad', function(val) {
if (val) {
console.log("it fired ... why?");
}
});
watchers are running with every digest cycle. as you are watching primitive value, attached to the scope, you need to verify that a new value exists (and has changed).
you could also do
$scope.$watch( 'kwhefjkewgfweahhfwujad', function(newVal, oldVal) {
if (newVal !== oldVal) {
console.log("it fired ... why?", newVal, oldVal);
}
});

$watch callback execution order

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.

$watch API description - first function described as listener function, why?

I'm reading through documentation on scope's method $watch here. The method accepts:
$watch(watchExpression, [listener];
Then they provide examples:
// let's assume that scope was dependency injected as the $rootScope
var scope = $rootScope;
scope.name = 'misko';
scope.counter = 0;
expect(scope.counter).toEqual(0);
scope.$watch('name', function(newValue, oldValue) {
scope.counter = scope.counter + 1;
});
expect(scope.counter).toEqual(0);
scope.$digest();
// the listener is always called during the first $digest loop after it was registered
expect(scope.counter).toEqual(1);
scope.$digest();
// but now it will not be called unless the value changes
expect(scope.counter).toEqual(1);
scope.name = 'adam';
scope.$digest();
expect(scope.counter).toEqual(2);
// Using a listener function
var food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
scope.$watch(
// This is the listener function --------- WHY ?????????????
function() { return food; },
// This is the change handler ---- THIS SHOULD BE A LISTNER FUNCTION
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
// Only increment the counter if the value changed
scope.foodCounter = scope.foodCounter + 1;
}
}
);
// No digest has been run so the counter will be zero
expect(scope.foodCounter).toEqual(0);
// Run the digest but since food has not changed count will still be zero
scope.$digest();
expect(scope.foodCounter).toEqual(0);
// Update food and run digest. Now the counter will increment
food = 'cheeseburger';
scope.$digest();
expect(scope.foodCounter).toEqual(1);
What I don't understand is that why they refer to function() { return food; } in the second example as a // This is the listener function if this is the function that should return the value that will be watched. which is a watchExpression?
The comment is a little bit misleading and you might file an issue or pull request to improve it.
As you said correctly there are 2 arguments for $watch(watchExpression, [listener])
watchExpression
The watchExpression is called on every call to $digest() and should return the value that will be watched.
The watch expression can be either a string or a function.
If you specify a function this is not the listener but rather a comparison function which will be called many many times so don't do any fancy stuff here ;)
This comparison function is used for the angular dirty checking.
More details on this can be found in this part of the video by the creator of angularJs: https://www.youtube.com/watch?feature=player_detailpage&v=ZhfUv0spHCY#t=1988
listener
This function is called whenever the value of your watchExpression changes.
So this a perfect place to update your models and do your fancy business logic

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