2

I have demonstrated my problem in a fiddle: http://jsfiddle.net/sljux/zVg7R/3/

I have an array of objects. For simplicity, lets say each object has a name, and an array of values, all observable:

self.array = [
    {
        name: ko.observable("first"),
        values: ko.observableArray([1, 2, 3])
    },
    {
        name: ko.observable("second"),
        values: ko.observableArray([2, 3, 4])
    },
    {
        name: ko.observable("third"),
        values: ko.observableArray([3, 4, 5])
    }
];

From that array I need to filter out certain objects, and add an additional computed to each one, which computes the sum of all values. The filtering part isn't important to the problem, so it won't be done. The problem is that the observable needs to be a property of each object, and it needs to reference that object's values array.

My best try was:

self.newArray = ko.observableArray([]);

for(var i = 0; i < self.array.length; i++) {
    var obj = {
        name: ko.observable("new " + self.array[i].name()),
        values: ko.observableArray(self.array[i].values())
    };

    obj.sum = ko.computed({
        read: function() {
            var res = 0;

            for (var j = 0; j < obj.values().length; j++)
                res += obj.values()[j];

            return res;
        },
        owner: obj
    });

    self.newArray.push(obj);
}

The problem is that the reference to the values observable array is somehow lost. That is, on the first computing, each object gets the sum of his values, but in the end, each computed computes the sum of the last object in array.

I tried with, and without the owner part of the computed, the reference is still transferred. The bug is obviously visible in the fiddle, where I have set up three buttons that change each values array.

I also tried setting it up as a class:

function Obj(name, values) {
    ...
}

self.newArray.push(new Obj("first", [1, 2, 3]);

but the same thing happens.

1 Answer 1

2

Sort answer: use ko.utils.arrayForEach instead of the manual for loop:

ko.utils.arrayForEach(self.array, function(item) {
    var obj = {
        name: ko.observable("new " + item.name()),
        values: ko.observableArray(item.values())
    };

    obj.sum = ko.computed({
        read: function() {
            var res = 0;

            for (var j = 0; j < obj.values().length; j++)
                res += obj.values()[j];

            return res;
        },
        owner: obj
    });

    self.newArray.push(obj);
});

Demo JSFiddle.

Long answer: you've bitten by the fact that in JavaScript the variables are function scoped so your for loop it does not create three local obj variable but reuses the same one variable and this combined with how closures work you will end up with all your computed referencing the last value:

You can solve this with wrapping the for body in an immediately executed function:

for(var i = 0; i < self.array.length; i++) {
    (function(){
    var obj = {
        name: ko.observable("new " + self.array[i].name()),
        values: ko.observableArray(self.array[i].values())
    };

    obj.sum = ko.computed({
        read: function() {
            var res = 0;

            for (var j = 0; j < obj.values().length; j++)
                res += obj.values()[j];

            return res;
        },
        owner: obj
    });

    self.newArray.push(obj);
    })();
}

Demo JSFiddle.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a bunch! I'm still getting used to JavaScript scopes. I actually did use ko.utils.arrayForEach, but only for computing the sum.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.