Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infinite loop when converting knockout object to plain JavaScript object

Tags:

knockout.js

Folliwing instructions given in the documentation, I have the following view model:

var newContactViewModel = function () {
    var self = this;

    self.Name = ko.observable();
    self.Address = ko.observable();
    self.City = ko.observable();
    self.State = ko.observable();
    self.PostalCode = ko.observable();

    self.Save = function () {
        $.ajax({
            type: "POST",
            url: "/contact",
            data: ko.toJS(self), //infinite loop here
            success: state.OnSaved
        });
    };
};

When the self.Save method is called, an infinite loop occurs. Chrome actually reports the error as:

Uncaught RangeError: Maximum call stack size exceeded

If I use ko.mapping.toJS(self) instead of ko.toJS(self), then I get a slightly more revealing error, but no real error "message":

infinite loop error

If I swap ko.toJS(self) out with something like { Name: self.Name(), Address: self.Address() /* etc */ }, then everything works fine. It seems like it's trying to convert the Save method and re-calling the method as a result.

There's either a bug in KnockoutJS or there's a problem when how I'm using it. I'd prefer the latter. Thoughts?

like image 616
Byron Sommardahl Avatar asked Sep 05 '12 19:09

Byron Sommardahl


1 Answers

I found out the problem with the code. ko.toJS preserves functions in the object so it is able to map it fine. However, jquery will invoke all functions on the data object to attempt to get a value. This in turn causes the infinite loop.

You need to flag the Save function to not be mapped. Unfortunately the toJS function doesn't appear to be able to do that. It will keep all members of the object. The mapping plugin fortunately allows you to do that.

To exclude a member, set the ignore option to an array containing 'Save' and it will ignore the Save member when mapped.

var data = ko.mapping.toJS(self, {
    ignore: ['Save']
});

If you're not using the mapping plugin, you always have the option to remove the Save function from the JS object.

self.Save = function () {
    $.ajax({
        type: "POST",
        url: "/contact",
        data: self.ToData(),
        success: state.OnSaved
    });
};
self.ToData = function () {
    var data = ko.toJS(self);
    delete data.Save; // remove functions
    delete data.ToData;
    return data;
};

On the other hand, I would suggest not trying to use the view model instance as the data to your calls. I would explicitly create the data object instead. Sure it has the potential to be very long, but it's an approach that will very likely always work.

self.ToData = function () {
    return ko.toJS({
        Name: self.Name,
        Address: self.Address,
        City: self.City,
        State: self.State,
        PostalCode: self.PostalCode
    });
};
like image 194
Jeff Mercado Avatar answered Oct 25 '22 22:10

Jeff Mercado