Knockoutjs Binding Syntax Clarification

October 6th 2013 by Samuel Rossille

A lot of knockoutjs beginners are puzzled by the many different ways to use a context property from inside a knockout data-bind attribute:

Syntactic Sugar in builtin binding handlers

The first thing to understand is that there is syntactic suggar inside Knockoutjs builtin binding handlers. Let's say we have this view model:

var viewModel = {
    firstName: ko.observable(),
    lastName: ko.observable()
};

When you write: <span data-bind="text: firstName"><span>, what happens is the following:

  1. The Knockoutjs engine parses your binding and evaluates the expression after text: to an observable, because it's just the content of the property. At this point, the Knockoutjs engine doesn't care about the fact that it's an observable. It could be a a number, a date, or a dinosaur, it doesn't care at all. It just passes the value to the text binding handler.
  2. After that, the text binding handler (builtin) receives an observable, and decides that the only smart way to transform an observable into a text is to attempt atoString() on whatever's inside. It is here that the observable is evaluated.

The text binding handler is not the only one that works this way. Many builtin binding handlers allow you to put either an observable or a value inside. So the unsuspecting beginner goes on thinking there is not much difference between <span data-bind="text: firstName"></span> and <span data-bind="text: firstName()"></span> until one day he uses a more complicated viewmodel:

var carViewModel = {
    owner: ko.observable({firstName: "John", lastName: "Doe"}),
    model: ko.observable()
};

... with the following databiding:

<span data-bind="text: owner.firstName"></span>

And there he will struggle for one hour trying to understand why on earth the span remains blank, why ? Why !? WHY ?!!???

It's just because knockout observables don't have a firstName property. So the expression evaluated to undefined, and not to the expected text. And when he receives this undefined, there is nothing the binding handler can do to workaround the fact that the observable has not been evaluated when it should have been. The right way to write the binding in this case is:

<span data-bind="text: owner() ? owner().firstName : 'N / A'"></span>

Note that the abscence of a value inside owner has to be handled.

Optional properties of the view model

Another source of complexity is that sometimes the bindings have to support optionnal properties in the viewmodel. Let's consider the following view model:

var carViewModel = {
    owner: ko.observable(), // will be filled later with the result of an ajax request.
                            // {firstName: "John", lastName: "Doe"}
    model: ko.observable()
};

and this binding:

<div>
    Model: <span data-bind="text: model"></span>
    Owner: <span data-bind="if: owner, with: owner">
        <span data-bind="text: firstName"></span>
        <span data-bind="text: lastName"></span>
    </span>
</div>

Now let's say that lastName is optional. What will happen is a binding parsing error when the owner value has no lastName property. A classic way to workaround this problem is to use the $data variable, which refers to the current context while inside the binding.

<div>
    Model: <span data-bind="text: model"></span>
    Owner: <span data-bind="if: owner, with: owner">
        <span data-bind="text: $data.firstName || 'N / A'"></span>
        <span data-bind="text: $data.lastName || 'N / A'"></span>
    </span>
</div>

Of course this is a workaround, and it's cleaner to have the lastName property available, with a null. But sometimes workarounds are needed and this one is at the origin of this syntax.

Conslusion

So what's the meaning of each sytax and when to use it?

My Recommendation

Abusing of the flexibility of knockoutjs can lead to misunderstanding and painful debugging sesions. So here are the rules that I strive to apply: