Newbie quesiton, I read the document of rootscope.$watch. the syntax of $watchis
$watch(watchExpression, [listener], [objectEquality], [deregisterNotifier]);
But I see the examples use it like below.
var food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
scope.$watch(
// This is the listener function
function() { return food; },
// This is the change handler
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
// Only increment the counter if the value changed
scope.foodCounter = scope.foodCounter + 1;
}
}
);
What does it mean missing WatchExpression? thanks.
watchExpression can be both string or function - refer back to angularjs documentation as posted in the question. Given example as posted in the question will never trigger change handler because food is always undefined.
If you are using function as watchExpression, the function must return different value in order to trigger change handler. As documented in angularjs official document
Expression that is evaluated on each $digest cycle. A change in the
return value triggers a call to the listener.
Code Snippet
function Ctrl($scope) {
$scope.food = "Laksa";
$scope.foodCounter = 0;
$scope.$watch(
//this is watch expression using function
function (scope) {
return scope.food;
},
//this is listener - change handler
function (newValue, oldValue) {
console.log(newValue, oldValue);
if (newValue !== oldValue) {
// Only increment the counter if the value changed
$scope.foodCounter = $scope.foodCounter + 1;
}
});
}
example - http://jsfiddle.net/4atA2/2/
this listener function is equivalent to $scope.$watch('food',... because it don't have additional logic. You have flexibility to add additional logic checking to decide whether want to trigger change handler or not. For example, watch on two scope variable.
example - http://jsfiddle.net/5NLNa/5/
function (scope) {
if(scope.drink == "Milo") {
return scope.food;
}
},
Related
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;
}
}
);
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
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?
I figured out that the $scope.$watch does not get triggered, when the target of the $watch gets set to the same value as it currently holds.
I have created a sample JSFiddle (http://jsfiddle.net/VKHVq/) which shows the behavior.
Enter any value in the first input field (position_total). The total_before_discount gets adjusted as it should, and also the total_before_discount $watch triggers. As the discount percentage is 0%, the total_discount will always stay 0. Nevertheless the 0 gets assigned all the time to the $scope.total_discount, but unfortunately, the watch on the 'total_discount' does not get triggered. Am I doing something wrong or is this behavior intended?
For me this behavior looks not as intended, as we get newValue and oldValue within the $watch function and as can be seen in a lot of angular.js $watch examples, it is recommended to test if (newValue === oldValue) { return }.
HTML
<div id="container" ng-controller="MyCtrl">
<div>Position total: <input type="number" ng-model="position_total"/>
<div>Total before discount: {{total_before_discount}}</div>
<div>Discount (in %): <input type="number" ng-model="discount"/>
<div>Total discount: {{total_discount}}</div>
<div>Total after discount: {{total_after_discount}}</div>
</div>
JS
var myApp = angular.module('myApp', ['ngAnimate']);
function MyCtrl($scope) {
$scope.position_total = 0;
$scope.total_before_discount = 0;
$scope.discount = 0;
$scope.total_discount = 0;
$scope.total_after_discount = 0;
calculatePositionTotal = function() {
// Dummy method to simulate position calculation
$scope.total_before_discount = $scope.position_total
};
calculateTotalDiscount = function() {
var total_discount = ($scope.total_before_discount / 100) * $scope.discount;
console.log('Going to set total_discount to ' + total_discount);
$scope.total_discount = total_discount;
};
calculateTotalAfterDiscount = function() {
$scope.total_after_discount = $scope.total_before_discount - $scope.total_discount;
};
$scope.$watch('position_total', function (newValue, oldValue) {
calculatePositionTotal();
});
$scope.$watch('total_before_discount', function (newValue, oldValue) {
calculateTotalDiscount();
});
$scope.$watch('discount', function (newValue, oldValue) {
calculateTotalDiscount();
});
$scope.$watch('total_discount', function (newValue, oldValue) {
console.log('total_discount $watch triggered...');
calculateTotalAfterDiscount();
});
}
The documentation says:
The listener is called only when the value from the current watchExpression and the previous call to watchExpression are not equal (with the exception of the initial run, see below).
So it's expected behavior.
The initial value of total_discount is 0 and when you setup the watch for the first time it gets triggered with oldValue and newValue as 0. After that the watch would not trigger till the value changes for total_discount. If you keep assigning it value 0, the watch would not trigger.
Watch would only get triggered when value changes, except in rare cases as mentioned in the documentation
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.
Is there a way to access the angular $watch iterator?
I want to capture the first iteration through a $watch like this:
scope.$watch("var", myFunc);
myFunc() {
if (iterator === 1) ...
}
Your watch function will get two arguments, the current value of "var" and the previous value. When your watch fires the first, both of these will be the same, so you can check for that to know if it's the first time it's called. Like this:
var myFunc = function(value, oldValue) {
if (value === oldValue) {
// First run
}
}