Consider the following ko bindings on a select element:
<select data-bind="value: valueObservable, options: optionsObservableArray,
optionsCaption: '[None] - this is an optional field'">
... given a viewmodel like the following:
function MyViewModel()
{
var self = this;
self.valueObservable = ko.observable();
self.optionsObservableArray = ko.observableArray();
// ajax call to load options
ko.computed(function() {
$.ajax(...)
.success(function(optionsResponse) {
self.optionsObservableArray(optionsResponse)
});
});
// ajax call to load data value
ko.computed(function() {
$.ajax(...)
.success(function(valueResponse) {
self.valueObservable(valueResponse)
});
});
}
What's weird about this is when the second (data) ajax call returns before the first (options) ajax call. Since the select element has an optionsCaption binding, here is what I believe is happening:
- The data ajax call completes, setting the
valueObservableto some value (like 6, abc, or some other non-falsy value). - Because there is only 1
optionin theselectelement (due to theoptionsCaption), and because thevalueObservableis bound to it (via thevaluebinding), this causes thevalueObservableto be changed toundefined. - Finally, the
optionsObservableArraycompletes and adds newoptionelements to theselect, but by this time it is too late: ThevalueObservableis now wrapping anundefinedvalue rather than the real data value returned from the first ajax call.
Question: What is the best way to work around this? Here is what I can think of:
- Make the first ajax call run with
async: false. This may slow page rendering. - Create a separate observable for binding to the select value (such as
value: selectedValueObservable). Then subscribe to theoptionsObservableArrayand use the subscription to do something likeself.selectedValueObservable(self.valueObservable()). This seems like a bandaid fix. - Render the select & options to the page before any javascript executes by sending the options data from the server (using MVC viewmodel). This makes it a little more difficult to deal with the options as an
observableArray.
Update
There is another concern which I omitted from this question to simplify the example code. In actuality, this viewmodel is used to create an editable list of data items. So there is actually more than 1 dropdown list that gets rendered to the page. The data ajax call really returns an array, and its success function really sets an observableArray of complex items. Since the dropdown list options are reused in every inline form, it is placed on the $parent viewmodel and only loaded once. It is also difficult to pass the select options in a single ajax call because the data items are retrieved via WebAPI (which returns an IEnumerable, no room to send additional dropdown options).
computed? - vittoreko.computedcauses it to be executed immediately. Basically it initializes the viewmodel by sending the ajax calls off when the viewmodel is constructed. - danludwigcomputeddoesnt seem to be designed for that. - vittore