0

I am reading a book where I have encountered following code (there is a 2D arrays called products which has 3 rows)-

html

<div unordered-list="products" list-property="price | currency"></div>

and JS

angular.module("exampleApp", [])
.directive("unorderedList", function () {

return function (scope, element, attrs) {

    var data = scope[attrs["unorderedList"]];
    var propertyExpression = attrs["listProperty"];

    if (angular.isArray(data)) {

        var listElem = angular.element("<ul>");
        element.append(listElem);

        for (var i = 0; i < data.length; i++) {

            var itemElement = angular.element('<li>');
            listElem.append(itemElement);

            var watcherFn = function (watchScope) {             
                                return watchScope.$eval(propertyExpression, data[i]);               
                            }

            scope.$watch(watcherFn, function (newValue, oldValue) {
                itemElement.text(newValue);
            });
        }
    }
}
})

The book says, "AngularJS evaluates the three watcher functions, which refer to data[i] after the loop terminates". "By the time loop terminates, the value of i is 3, and this means that all three watcher functions try to access an object in the data array that doesn’t exist, and that’s why the directive doesn’t work".

The watcher function is inside the loop and called from scope.$watch so why it is so?

  • For the same reason of https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example – guy mograbi Nov 07 '17 at 17:35

2 Answers2

0

This is due to how javascript's variable scope works.

You can read a full answer on that at : JavaScript closure inside loops – simple practical example

Well, the problem is that the variable i, within each of your anonymous functions, is bound to the same variable outside of the function.

The same applies to javascript watchers, which are applied on a different tick. tick meaning next event loop execution. To know more about this, read more about angular's digest cycle.

So the value of the variable has already changed until the function is executed.

Simple Solution 1 - specific to angular

An easy solution specific for angular would be to construct a string expression for $watch.

So $scope.$watch('products[1].price | currency', ..) should work. Translating it to variables according to your snippet

 $scope.$watch(`${attrs['unorderdList']}[${i}].${attrs['listProperty']}`, function(newValue){ ... }) 

Or something similar should work. I will need more information to provide a running example.

Simple Solution 2 - none angular specific

Another solution, not specific for angular, would be to bind it to a different scope. So if the first line of the loop would be

for (let i = 0; i < data.length; i++) {

Just replace var with let - and it will work. This solution takes advantage of new JavaScript let syntax which has a block scope rather than function scope.

   for (let i = 0; i < data.length; i++) {
        var itemElement = angular.element('<li>');
        listElem.append(itemElement);

        var watcherFn = function (watchScope) {             
                            return watchScope.$eval(propertyExpression, data[i]);               
                        }

        scope.$watch(watcherFn, function (newValue, oldValue) {
            itemElement.text(newValue);
        });
    }
guy mograbi
  • 27,391
  • 16
  • 83
  • 122
0

It happens because these functions will work with closure that represents the state of outer JS function scope on the moment of execution. That's why after your loop stops the variable i will refer to the last assigned value: 3.

To have variable i equal the value on the moment of iteration, you need to create intermediate scope wrapping it by additional function:

var watcherFn = (function(i)
  return function (watchScope) {             
      return watchScope.$eval(propertyExpression, data[i]);               
  }
}(i));

You can read more about closures in JS in this answer: How do JavaScript closures work?

r.1
  • 101
  • 2