In the previous post, I suggested some ideas for binding against multiple view models in Knockout.js. When working with more than one view model, a common task is deciding how to communicate between the separate components. Creating direct references between them often doesn’t feel right and can lead you down a path where each view model has lost its independence and cannot be used effectively without the other objects.
To handle this situation, we can keep our components loosely coupled by using a messaging system where each view model or component does not need to have direct references to its counterparts. There are several benefits to using this technique:
- Components remain independent. They can be developed, tested, and used in isolation.
- Knockout view models and non-KO components can communicate using a common interface without direct knowledge of each other.
- Components can be safely refactored. Properties can be renamed, moved, and adjusted without worrying about breaking compatibility.
Knockout’s native pub/sub
There are already many libraries and options for providing this type of message bus. One option might be triggering custom events or using great pub/sub libraries like amplify.js or postal.js. While these libraries provide robust capabilities, Knockout itself already has all of the tools to do basic pub/sub communication built-in. This is part of the ko.subscribable type, which has its capabilities added to
computed observables, and
Normally, you would not construct a
ko.subscribable directly, but it is easy enough to do so:
In Knockout 2.0, support was added for topic-based subscriptions to aid in sending out beforeChange notifications. To create a topic-based subscription against our
ko.subscribable, we would simply do:
1 2 3 4 5 6
To send out a notification on this topic we would do:
Now, we can do basic pub/sub messaging using
notifySubscribers against our mediator, the
postbox object. However, whenever I explore integrating a new technique with Knockout, I try to consider how to make it feel as easy and natural as possible.
A typical scenario for this type of functionality would be that we want to synchronize an observable between view models. This might be a one-way or even a two-way conversation. To make this easy, we can look at extending the
ko.subscribable type, which would affect
Suppose that we want to setup an observable to automatically publish on a topic whenever it changes. We would want to set up a manual subscription against that observable and then use
notifySubscribers on our
1 2 3 4 5 6 7 8 9 10
Now, whenever the observable’s value changes, it will publish a message on the topic.
On the other side, we might want to make it easy for an observable to update itself from messages on a topic. We can use this same concept:
1 2 3 4 5 6 7 8
Notice that we can just pass
this (the observable) in as the callback to the
subscribe function. We know that an observable is a function and that it will have its value set when you pass an argument into the function. So, there is no need to write something like:
1 2 3 4
Now, our observables can exchange information without direct references to each other. They do need to agree on a topic, but do not have any knowledge about the internals of the other view model or component. We can even mock or simulate the other components in testing scenarios by firing messages.
Potential gotcha: publishing objects
If the new value being published is an object, then we need to be careful, as both sides will have a reference to the same object. If code from multiple view models makes changes to that object, then we are likely no longer as decoupled as we would like. If the object is simply configuration/options passed as an object literal that are not modified, then this seems okay. Otherwise, it is preferable to stick to primitives in the values being published. Another alternative is to use something like
ko.toJS to create a deep-copy of the object that is being published, so neither side has the same reference.
A new library: knockout-postbox
I created a small library called knockout-postbox to handle this type of communication. It uses the techniques described above and adds a bit of additional functionality:
- creates a
ko.postboxobject with clean
publishmethods that take a topic as the first argument.
- adds a
subscribeTofunction to all subscribables (observables, observableArrays, and computed observables). The
subscribeTofunction also allows you to initialize your observable from the latest published value and allows you to pass a transform function that runs on the incoming values.
- adds an
unsubcribeFromfunction to all subscribables that removes subscriptions created by
- adds a
publishOnfunction to all subscribables that automatically publishes out new values. The
publishOnfunction also allows you to control whether you want to immediately publish the initial value, and lets you supply an override function (
equalityComparer) that allows you to compare the old and new values to determine if the new value should really be published.
- adds a
stopPublishingOnfunction to all subscribables that removes the subscription that handles the
- adds a
syncWithfunction that does both a
publishOnon the same topic for two-way synchronization.
Project link: https://github.com/rniemeyer/knockout-postbox
Next post: using this concept to integrate with client-side routing in a way that is natural to Knockout