| 5 min read
KnockoutJS is a beautiful JavaScript framework that helps you to create responsive and data rich user interface without making the JavaScript code dirty. When you start learning KnockoutJS, you tend to make habit of creating single a View Model and binding it globally or to one particular DOM and play with it.
Once you start diving deep, you will realize, single view model is not enough for developing enterprise applications. It does not help you maintaining the modularity of an application. Bigger the application gets, complexity of the view model increases. You will literally give up as the code will become hard to maintain and even harder to test.
Why one should use multiple view models? Answer is simple, it maintains the modularity. Multiple view models act as modules. You basically divide big problem into small modules which are reusable and easily extensible. You preserve ability to maintain an application by decoupling the components to the most granular level. The app flow becomes so good with introduction of multiple view models in your app.
However, there is a major and most discussed problem with this approach on KnockoutJS section of StackOverflow, Knockout forums (common question with MVVM pattern in general). The communication between multiple view models. Surprisingly, this problem has not been addressed or discussed at all in KO documentation even though people face it quite often. I end up helping at least couple of KnockoutJS users / developers on Stack Overflow having the same problem.
Yes, we don't have out of the box and easy to use solution on communication between multiple view models which are bound in a single viewport but with different DOM elements like the code below.
var self = this; | |
self.firstName = ko.observable(); | |
self.lastName = ko.observable(); | |
self.fullName = ko.computed(function(){ | |
return self.firstName + " " + self.lastName; | |
}); | |
}; | |
var viewModel2 = function(){ | |
var self = this; | |
self.premium = ko.observable(); | |
}; | |
ko.applyBindings(new viewModel1(), document.getElementById("container1")); | |
ko.applyBindings(new viewModel2(), document.getElementById("container2")); |
There are two correct approaches of maintaining communication between multiple view models.
First way of achieving communication between multiple view models is to introduce master view model.
// view model 1 definition | |
var viewModel1 = function(){ | |
this.firstName = ko.observable("Wrapcode"); | |
this.messageForVM2 = ko.observable("Hello from first view model"); | |
this.message = ko.observable("Hello this is vm1") | |
}; | |
//view model 2 definition | |
var viewModel2 = function(vm1){ | |
this.firstName = ko.observable(vm1.firstName()); | |
this.message = ko.observable(vm1.messageForVM2()); | |
}; | |
//master view model with instances of both the view models. | |
var masterVM = (function(){ | |
this.viewModel1 = new viewModel1(), | |
this.firstName = "Rahul", | |
this.viewModel2 = new viewModel2(this.viewModel1); | |
})(); | |
ko.applyBindings(masterVM) |
Live action on JSFiddle : https://jsfiddle.net/rahulrulez/paxnd6uz/1/
With this approach, it's easy to get / post information from / to instance of other sub view models in master view model. But the problem of publishing changes still persist. Although we found a way to exchange data between multiple view models, the communication is still passive, if we change something in input box in the fiddle above, it does not get reflected in to another view model. That's where pub/sub pattern, our next approach plays an important role.
Well, if you go with master view model approach, you will still have to refer / pass one view model into another to communicate. The communication is still pretty much passive as master view model approach does not observe or track the changes. To overcome this limitation, KnockoutJS has its native PubSub function - ko.subscribable
which is not at all documented in detail. Here's how you can extend your application to integrate PubSub.
var shouter = new ko.subscribable();
Subscribable is inherited so it is available globally irrespective of any view model scope. But it's better to construct it globally so that you don't have to create multiple instances of same thing.
Now, notify the changes to shouter (subscribable) by using Knockout's subscriber function. In the above example (refer JSFiddle), we want to publish the value this.messageForVM2
from viewModel1 so that it will be accessible in second view model. We can achieve this by notifying subscribers in subscription function of this.messageForVM2
Learn more about Subscribe.
this.messageForVM2.subscribe(function(newValue) { | |
shouter.notifySubscribers(newValue, "messageToPublish"); | |
}); |
Remember, ko.subscribable.notifySubscribers takes two parameters.
To be able to catch published values and updates, you need to subscribe to the particular topic. Whenever the change gets triggered, it invokes the subscriber function. We can write the needed logic inside subscriber function. Subscribable exports method subscribe which expects three parameters
shouter.subscribe(function(newValue) { | |
this.message(newValue); | |
}, this, "messageToPublish"); |
Working fiddle
Try typing something in the message box, subscribable will publish the message as soon as observable tracks changes. Now the communication is active and decoupled. We do not have any sort of reference in both view models.
You can do a lot with KO's native PubSub. You should definitely try Ryan Niemeyer's very useful extension called Postbox. It extends simple pub/sub capabilities to the next level by introducing various customizations like syncing multiple observables / components from multiple view models, publishing messages on initialization, clean subscription and unsubscribing abilities etc.
I hope this article will help to give you a little head start if you are planing to decouple modules without losing communication in your Knockout application.
Peace, RP