The problem
As I was working and exploring knockoutjs, I got stuck at some point. I want to serialize a viewmodel (and underlying viewmodels) to JSON. This will end up in an infinite loop because the child viewmodels have a property which references the parent viewmodel. What is the best practice for solving this issue?
The code
var Partner = function (parent) {
var self = this;
self.parent = parent;
self.name = ko.observable('');
}
var ProjectViewModel = function () {
var self = this;
self.nr = ko.observable(0);
self.tite = ko.observable('');
self.partners = ko.observableArray();
self.addPartner = function () { self.partners.push(new Partner(self)) };
self.removePartner = function (c) { self.partners.remove(c) };
};
var vm = new ProjectViewModel();
ko.applyBindings(vm);
$("#button").click(function () {
alert(ko.toJSON(vm));
}
What I tried so far
I tried adding the following method in the Partner viewmodel:
Partner.prototype.toJSON = function () {
var copy = ko.toJS(self);
delete copy.parent;
return copy;
}
This works with only one Partner, if the ProjectViewModel has multipe partners, every partner will have the same value as the last partner. This happens only when I want to serialize it to JSON.
There are several ways to handle this situation. The
ko.toJSpart of KO handles this properly. It is ultimatelyJSON.stringify(called afterko.toJSinko.toJSONthat causes the error.Your
toJSONmethod on your prototype is pretty close, except that you want to be dealing withthisrather thanself.So, it would look like:
Other ways to handle it:
1- don’t actually store your parent on the child object and just reference it in any handlers directly based on the argument passed to the constructor.
2- “hide” your parent reference
You can place your
parentvalue behind a function (could be as a sub-property of an observable). When your structure is turned into a plain JS object byko.toJS, then any sub-properties of functions are lost.3- change the structure of your application, depending on what you want to do. Suppose that the parent wants to react whenever a child has it’s name changed. You could pass in a callback, setup the subscription, and execute it whenever it changes