Best Parts of Durandal.js - Compose binding

I wanted to start a quick series dedicated to what makes Durandal so powerful to yield.

Part 1 - 

The Compose binding

With Durandal, you can dynamically load views and AMD modules directly from the DOM.  Why is this important?

  1. You can separate your views and modules into the smallest bits of (re-usable) code needed.
  2. Render your content more dynamically.
  3. You can easily instantiate and show either Singleton objects or Constructors.  (wait what?)

Separating your code

Imagine you want to create 'n' number of widgets that are in charge of their own visibility, data-fetching, and data-binding.  Easy.  Using the compose binding with a foreach, you can instantiate the modules in your view model and use the DOM to render them.

View -

<ul data-bind="foreach: widgets">
     <li data-bind="compose: widgetPath"></li>
</ul>

View Model -

define([], function () {
    var widgets = ko.observableArray();
    function initializeViewModel() {
        widgets.push(new Widget('Compose a widget module', 'viewmodels/exampleone'));
        widgets.push(new Widget('Compose a widget view only', 'exampletwo'));
    }
    function activate() {
        initializeViewModel();
    }
    function Widget(title, path) {
        var self = this;
        self.Title = title;
        self.widgetPath = path;
    }
});

What did we just do?

  1. When we initiated the view model, we added two widgets into an observable array containing widgets.
  2. The first widget is a path to a view model, which Durandal finds an associated view for and properly recognizes as part of a module.
  3. The second widget is a path to a view, with no corresponding view model.

Whats interesting to note here is that in the first widget with a corresponding view model, Durandal helps us by creating a context around that view that is bound to the view model. This means if we reference something in the view model that Knockout and Durandal bind any related view elements to that instance of the view model. This is most ideal when not using singletons, as the every instance of a singleton will be bound to the same context.

In the second widget we are simply binding to the current context, which in this case would be the widget.  This is derived from the foreach binding that the widget is nested inside of, so you could easily bind to the title property or you could also reference the parent context using the Knockout $parent reference.

The reason $parent would not work in the first example is that the new context does not directly know who the parent is.  What if we wanted it to?  That's easy as well, using activationData -

Activation Data -

Using the activationData option of the compose binding, we can inject data or even context into the composed view model.  This is an excellent way of injecting dependency, and I could go on for hours on the various ways you can use this in your application, but let's keep it simple and show an example.

Let's bind our view up to widgets using Knockout's containerless binding -

<!-- ko foreach: widgets -->
    <!-- ko compose: { model: widgetPath, activationData: { data: $data } } -->
    <!-- /ko -->
<!-- /ko -->

You'll notice that I passing an object into the compose binding that contains a model and activationData, which contains an object of it's own. You can read more on the Durandal website on the various parameters (http://durandaljs.com/documentation/Using-Composition.html) but basically the model is declaring that at that path, there is a view model to use for binding. Durandal automatically finds the corresponding view, and then injects our activationData into the view model. To intercept it, you only need to have a parameter in your activate method of the widget. Notice I am injecting $data, which basically just injects the current context into the view model. You could just as easily inject strings, observables, and even parent contexts.

Grabbing the data in our widget's view model -

define([], function () {
    var thisContext = ko.observable();
    function activate(activationData) {
        // Set thisContext equal to the injected data
        thisContext(activationData.data);    
    }
    function Widget(title, path) {
        var self = this;
        self.Title = title;
        self.widgetPath = path;
    }
});

Now you can bind thisContext in the view however you want. A solid strategy is to use the with binding in your view that is set to thisContext, that way if for some reason your view model is instantiated without data it will just appear empty as opposed to breaking your bindings.

That's it! Feel free to leave comments if you notice any problems or have any suggestions or questions. If you have any Durandal questions please direct them to StackOverflow.com or the Durandal.js Google Group, where the active community will help resolve them.