0

How can I create ko.computed variables in a loop such that on re-computation the value of the loop's variables inside the computed function are the same as they were when the computed function was first defined?

This is the way I expect closures to work, but:

http://jsbin.com/dileju/6/edit?html,js,output (enter a number other than 1 in to the input box and press return)

the value used on re-computation is the value of the variable in the last iteration of the loop.

sbs
  • 1,139
  • 3
  • 13
  • 19
  • The question is: Why? Can you outline the situation that makes this necessary? – Tomalak Feb 26 '16 at 16:41
  • The example should make that clear. I'm getting data from a server and want to display some properties of each datum along with a computed value, and that computed value depends on user input and properties of the datum – sbs Feb 26 '16 at 16:43
  • 1
    This is directly related to the way closures work in JavaScript: http://stackoverflow.com/questions/111102/how-do-javascript-closures-work – Andrew Whitaker Feb 26 '16 at 16:49

2 Answers2

1

Get rid of the for loop, it's messing things up (because the way how closures work, as @Andrew has pointed out correctly in the comments).

var ViewModel = function() {
  var self = this;
  self.multipler = ko.observable(1);
  self.things = ko.observableArray();

  self.fakeServerData = [
    { id: 1, properties: { name: '1', val: 1 }},
    { id: 2, properties: { name: '2', val: 2 }},
    { id: 3, properties: { name: '3', val: 3 }}
  ];

  ko.utils.arrayForEach(self.fakeServerData, function (item) {
    var props = item.properties,
        multi = +self.multipler();

    props.computed = ko.pureComputed(function () {
      return multi * props.val;
    });
  });
};

ko.applyBindings(new ViewModel());

The rule of thumb is: Don't make functions in a loop. If you have to create functions, don't use a loop.

The way to avoid a loop is to use the native array functions (Array.prototype.forEach) or their equivalents from various libraries, like knockout's own ko.utils.arrayForEach(), jQuery's $.each or underscore/lodash's _.forEach and others.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
1

For what you're doing, you don't really need a computed for every element in your array. You can use a function and pass it the current element. A function works fine in a binding. Knockout will keep track of its observable dependencies.

So your binding would be

<span data-bind="text: $parent.thingy($data)"></span>

The function would be:

self.thingy = function (data) {
  var mult = parseInt(self.multipler());
  return mult * data.val;
};

And you'd build your things list like:

for (var i = 0; i < self.fakeServerData.length; i++) {
  var props = self.fakeServerData[i].properties;
  self.things.push(props);
}
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • Good idea, but unfortunately in the real code I use the computed value multiple times (in switch, if, css statements and filters and sorts), and doing it this way calls the function many times (and the real function is not so trivial as in the example) – sbs Feb 27 '16 at 08:02