Domanda

Fiddler here: http://jsfiddle.net/u9PLF/

I got a list of parents with nested children. I periodically update the whole object hierarchy and update the first parent's children array once. The problem I'm having is that it seems like the children array is always being updated, or at least it notifies the subscribers every time I call the fromJS function.

I expect the children observableArray to only notify the subscribers once (when it first change from [A,B] to [A,B,C], not on subsequent calls).

What am I doing wrong?

Thanks

Code:

var data = {
    parents: [{
        id: 1,
        name: 'Scot',
        children: ['A', 'B']
    }]
};

function ParentVM(data) {
    var self = this;

    ko.mapping.fromJS(data, {}, self);

    self.count = ko.observable(0);

    self.hello = ko.computed(function () {
        console.log("Update", self.children());
        self.count(self.count() + 1);
    });
}

var mapping = {
    parents: {
        create: function (options) {
            return new ParentVM(options.data);
        },
        key: function (data) {
            return ko.utils.unwrapObservable(data.id);
        }
    }
};

var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);

setInterval(function () {

    var data = {
        parents: [{
            id: 1,
            name: 'Scott',
            children: ['A', 'B', 'C']
        }]
    };

    ko.mapping.fromJS(data, mapping, viewModel);
}, 2000);

viewModel.parents.subscribe(function () {
    console.log("Parents notif");
});
È stato utile?

Soluzione

@Charlie is right. This is not related to ko.mapping, it's how ko.observable tests equality of values.

http://jsfiddle.net/C7wUb/

In this demo, items are updated every time when calling items([1,2])

var vm = {
    items: ko.observable([1,2]),
    count: ko.observable(0)
};

vm.items.subscribe(function(new_v) {
    console.log("items are updated to " + new_v);
    vm.count(vm.count() + 1);
});

ko.applyBindings(vm);

setInterval(function() {
    vm.items([1,2]);
}, 2000);

The deeper cause is the default implementation of ko's equalityComparer, it only compares primitive types.

// src/subscribables/observable.js
ko.observable['fn'] = {
    "equalityComparer": valuesArePrimitiveAndEqual
};

// src/subscribables/extenders.js
var primitiveTypes = { 'undefined':1, 'boolean':1, 'number':1, 'string':1 };
function valuesArePrimitiveAndEqual(a, b) {
    var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
    return oldValueIsPrimitive ? (a === b) : false;
}

The way to avoid unnecessary update event on observableArray is to overwrite equalityComparer.

http://jsfiddle.net/u9PLF/2/

var oldComparer = ko.observable.fn.equalityComparer;

ko.observable.fn.equalityComparer = function(a, b) {
    var i, toStr = Object.prototype.toString;
    if (toStr.call(a) === '[object Array]' && toStr.call(b) === '[object Array]') {
        if (a.length !== b.length) return false;
        for (i = 0; i < a.length; i++) {
            if (!oldComparer(a[i], b[i])) return false;
        }
        return true;
    } else {
        return oldComparer(a, b);
    }
};

(you can also overwrite equalityComparer only for children observableArray, see how ko.extenders notify is implemented in src/subscribables/extenders.js)

BTW, the way you do self.hello = ko.computed(...) is kind of risky.

self.hello = ko.computed(function () {
    console.log("Update", self.children()); // you want to notify self.hello when children change.
    self.count(self.count() + 1); // but self.hello also has dependency on self.count()
});

I think what you want is a simple subscribe, register a callback for children change event.

self.children.subscribe(function(new_v) {
    console.log("Update", new_v);
    self.count(self.count() + 1);
});
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top