Difference between knockout View Models declared as object literals vs functions

Javascriptknockout.js

Javascript Problem Overview


In knockout js I see View Models declared as either:

var viewModel = {
    firstname: ko.observable("Bob")
};

ko.applyBindings(viewModel );

or:

var viewModel = function() {
    this.firstname= ko.observable("Bob");
};

ko.applyBindings(new viewModel ());

What's the difference between the two, if any?

I did find this discussion on the knockoutjs google group but it didn't really give me a satisfactory answer.

I can see a reason if I wanted to initialise the model with some data, for example:

var viewModel = function(person) {
    this.firstname= ko.observable(person.firstname);
};

var person = ... ;
ko.applyBindings(new viewModel(person));

But if I'm not doing that does it matter which style I choose?

Javascript Solutions


Solution 1 - Javascript

There are a couple of advantages to using a function to define your view model.

The main advantage is that you have immediate access to a value of this that equals the instance being created. This means that you can do:

var ViewModel = function(first, last) {
  this.first = ko.observable(first);
  this.last = ko.observable(last);
  this.full = ko.computed(function() {
     return this.first() + " " + this.last();
  }, this);
};

So, your computed observable can be bound to the appropriate value of this, even if called from a different scope.

With an object literal, you would have to do:

var viewModel = {
   first: ko.observable("Bob"),
   last: ko.observable("Smith"),
};

viewModel.full = ko.computed(function() {
   return this.first() + " " + this.last();
}, viewModel);

In that case, you could use viewModel directly in the computed observable, but it does get evaluated immediate (by default) so you could not define it within the object literal, as viewModel is not defined until after the object literal closed. Many people don't like that the creation of your view model is not encapsulated into one call.

Another pattern that you can use to ensure that this is always appropriate is to set a variable in the function equal to the appropriate value of this and use it instead. This would be like:

var ViewModel = function() {
    var self = this;
    this.items = ko.observableArray();
    this.removeItem = function(item) {
         self.items.remove(item);
    }
};

Now, if you are in the scope of an individual item and call $root.removeItem, the value of this will actually be the data being bound at that level (which would be the item). By using self in this case, you can ensure that it is being removed from the overall view model.

Another option is using bind, which is supported by modern browsers and added by KO, if it is not supported. In that case, it would look like:

var ViewModel = function() {
    this.items = ko.observableArray();
    this.removeItem = function(item) {
         this.items.remove(item);
    }.bind(this);
};

There is much more that could be said on this topic and many patterns that you could explore (like module pattern and revealing module pattern), but basically using a function gives you more flexibility and control over how the object gets created and the ability to reference variables that are private to the instance.

Solution 2 - Javascript

I use a different method, though similar:

var viewModel = (function () {
  var obj = {};
  obj.myVariable = ko.observable();
  obj.myComputed = ko.computed(function () { return "hello" + obj.myVariable() });
  
  ko.applyBindings(obj);
  return obj;
})();

Couple of reasons:

  1. Not using this, which can confusion when used within ko.computeds etc
  2. My viewModel is a singleton, I don't need to create multiple instances (i.e. new viewModel())

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevView Question on Stackoverflow
Solution 1 - JavascriptRP NiemeyerView Answer on Stackoverflow
Solution 2 - Javascriptpaulslater19View Answer on Stackoverflow