0
votes

I only started to use Knockout but I was already experienced with MVVM as applied in WPF. The issue I am encountering is that the my Knockout view model wrapping the model object does not update that original model. Here is a small example:

HTML

<select name="size" id="sel-size" data-bind="options: sizes, value: size"></select>

JS

// default settings
var settings = {
    size: 5
};

// settings view model
var settingsVM = function () {
    return {
        sizes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        size: ko.observable(settings.size)
    }
}();

// subscribe to changes
settingsVM.size.subscribe(function() {
    alert("Model: " + settings.size + " / VM: " + settingsVM.size());
})

ko.applyBindings(settingsVM);

View in JSFiddle

When the drop-down selection is changed, only settingsVM.size() from the view model is updated but settings.size from the model remains the same.

It seems that initializing an observable with a reference does not keep that reference as a backing field for the property accessor. What am I missing about the MVVM way of Knockout?

3

3 Answers

0
votes

When you change the value of the select, it is in effect over-writing the contents of your size observable. If you want to set up a backing-field type arrangement, you'll have to use a computed observable with read/write.

Also, if you do want to update the original 'settings' object, you'll need to deal with that rather than the settings.size property alone. Something like this will keep the original model in step with the select:

// settings view model
var settingsVM = function () {
  var self=this;
  self.settings = ko.observable({
    size: 5
  });
  self.sizes =   [{size:1},{size:2},{size:3},{size:4},{size:5}];
  self.size = ko.computed({
          read:function(){
            return self.settings();
          },
          write:function(val){
            self.settings(val);
   }});
   self.size.subscribe(function() {

            alert("Model: " + self.settings().size + " / VM: " + self.size().size);
        });
};

ko.applyBindings(new settingsVM());

JSFiddle

0
votes

The reason it is not updating is because your settings.size is not an observable and your code does not provide to use settings.size as selected size in html.

// default Model for settings
function settings(){
    this.size = ko.observable(5);
}

// settings view model
var settingsVM = function () {    
    return {
        sizes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        settings: new settings()
    }
}();

// subscribe to changes
settingsVM.settings.size.subscribe(function() {
    alert("model: " + settingsVM.settings.size());
})

ko.applyBindings(settingsVM);

see updated fiddle here

0
votes

The dirty little secret of Knockout is that it's not really MVVM. It doesn't have structure for data modeling separate from viewmodeling, so it's just VVM. In an ordinary app design, the data model is on the server, and the "model" in the app is a set of simple AJAX routines for bringing it pieces into the viewmodel. Knockout doesn't concern itself with AJAX, so how you handle that part of your app is up to you (but it's usually pretty simple).

In your question, you expect the model to automatically update to reflect the corresponding viewmodel element. That's redundant; it's not clear that you need something separate that just copies a value from the viewmode. The viewmodel can just subsume the model. But if you have more model-y stuff in mind to do, so that you do need your model elements to be collected together in a structure, Will Jenkins' suggestion of using subscribe is the most straightforward way of responding to changes in the viewmodel.

In the comments below, Will Jenkins brings up the utility functions Knockout provides to convert between JSON and observables. These are also helpful for migrating back and forth between a model and a viewmodel.

See also: ko.mapping