2

I'm trying to call a Api that returns Json. I'm attempting to put this returned data into a knockout observable array. My view model looks like this:

var adminData = $.getJSON("/api/administrators");
//console.log(adminData);

var viewModel = {
    administrators: ko.observableArray(adminData)
};

ko.applyBindings(viewModel);

The request goes through and an object is being returned with the expected data in adminData, but when I try to add it to ko.observableArray I get this in the console: The argument passed when initializing an observable array must be an array. I can't figure out how to get that data into an array for knockout.

rross
  • 2,236
  • 11
  • 36
  • 41

3 Answers3

10

$.getJSON in asynchronous. The result data is available in the callback only. It is not available as the return value of $.getJSON The documentation never mentions a return value.

$.getJSON("/api/administrators", null, function(adminData, status, xhr){
    var viewModel = {
        administrators: ko.observableArray(adminData)
    };  
    ko.applyBindings(viewModel); 
});

If you need to make separate AJAX calls, you should use jQuery.when See Wait until all jQuery Ajax requests are done?

$.when($.ajax("/api/administrators"), $.ajax("api/roles")).done(function(resp1, resp2){        
    ko.applyBindings({
        administrators: ko.observableArray(resp1[0]),
        roles: ko.observableArray(resp2[0]);
    }); 
});

Here are some other less than ideal solutions, but it shows you what happens under the hood.

If you don't mind that the requests wait for each other

$.getJSON("/api/administrators", null, function(adminData){
    $.getJSON("/api/administrators", null, function(apiRoles){
        ko.applyBindings({
            administrators: ko.observableArray(adminData),
            roles: ko.observableArray(apiRoles);
        }); 
    });
});

If you do care, it's more complicated since you need to track that the requests finished

var ajaxAdminData, ajaxApiRoles
$.getJSON("/api/administrators", null, function(adminData, status, xhr){
    var ajaxAdminData = adminData;
    // If the other call finished, apply the bindings
    if (ajaxApiRoles) {
        applyBindings();
    }
});

$.getJSON("/api/administrators", null, function(apiRoles, status, xhr){
    ajaxApiRoles = apiRoles;
    // If the other call finished, apply the bindings
    if (ajaxAdminData) {
        applyBindings();
    }
});

function applyBindings() {
    ko.applyBindings({
        administrators: ko.observableArray(ajaxAdminData),
        roles: ko.observableArray(ajaxApiRoles);
    }); 
}
Community
  • 1
  • 1
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • This way works great if you are only ever calling `$.getJSON` once, but if you ever want to put that `$.getJSON` chunk inside of a timer (for instance to keep the UI up to date when the server's data changes) you will run into some big issues as you will apply the bindings every time the `$.getJSON` gets called. In this case it would be best to keep the model and binding call outside the JSON retrieval code. Just use a global array to store the JSON in. – Zac Oct 22 '13 at 17:09
  • @Zac Suggesting globals as an approach is a cheap way out. I'm sure you can figure out how to do it without resorting to globals. – Ruan Mendes Oct 22 '13 at 17:45
1

Because getJSON() is asynchronous! You can not treat it as a synchronous method. Look at what that console.log line is, it would show why it failed.

Use the callback

$.getJSON("/api/administrators", function(adminData) {

    var viewModel = {
        administrators: ko.observableArray(adminData)
    };

    ko.applyBindings(viewModel);
}
epascarello
  • 204,599
  • 20
  • 195
  • 236
  • What if you wanted to add two models using this syntax? for example, I also want to make a call to /api/roles, and add that to the model and the observableArray. – rross Apr 16 '13 at 19:48
  • @rross The simplest way is to make your 2nd AJAX call from the callback. If you want to send them at the same time, you need to share variables between both handlers and both handlers need to make sure that both calls returned... See my updated answer in a minute – Ruan Mendes Apr 16 '13 at 20:13
0

In your $.getJSON callback just update your observable array then call valueHasMutated() on that array as in: administrators.valueHasMutated()

Craig Wilcox
  • 1,577
  • 1
  • 12
  • 23