rit
rit

Reputation: 2308

$scope.$watch of angular.js not triggered

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

}

Upvotes: 0

Views: 1964

Answers (2)

JB Nizet
JB Nizet

Reputation: 692131

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.

Upvotes: 4

Chandermani
Chandermani

Reputation: 42669

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.

Upvotes: 2

Related Questions