Control flow bindings
So far, we have looked at one-way and two-way bindings that set or sync data with an attribute on an HTML element. There is a different kind of binding that Knockout uses for modifying the DOM by adding or removing nodes. These are the control flow bindings, and they include foreach
, if
, with
, and template
.
All of the control flow bindings work by actually removing their content from the DOM and creating an in-memory template from it. This template is used to add and remove the content as necessary.
Control flow bindings (except if
) also introduce a binding context hierarchy. Your root binding context is the viewmodel passed to ko.applyBindings
. The data-bind
attributes have access to properties in the current context. Control flow bindings (other than if
) create a child-binding context, meaning that data-bind
attributes inside the control flow binding's template have access to the properties of their context and not the root context. Bindings inside a child context have access to special properties to allow them to navigate the context hierarchy. The most commonly used are:
$parent
: This accesses the binding context of the immediate parent. In this example,group
and$parent.group
refer to the same property because$parent
accesses the context outside of the person:<span data-bind="text: group"></span> <div data-bind="with: person"> <span data-bind="text: name"></span> <span data-bind="text: $parent.group"></span> </div>
$parents[n]
: This is an array of parent contexts. The$parents[0]
array is same as$parent
.$root
: This is the root viewmodel, the highest context in the hierarchy.$data
: This is the current viewmodel, useful insideforeach
loops.Note
For a complete list of context properties, see the Knockout documentation for them at http://knockoutjs.com/documentation/binding-context.html.
The if binding
The if
binding takes a value or expression to evaluate and only renders the contained template when the value or expression is truthy (in the JavaScript sense). If the expression is falsy, the template is removed from the DOM. When the expression becomes true, the template is recreated and any contained data-bind
attributes are reapplied. The if
binding does not create a new binding context:
<div data-bind="if: isAdmin"> <span data-bind="text: user.username"></span> <button data-bind="click: deleteUser">Delete</button> </div>
This div
would be empty when isAdmin
is false
or null
. If the value of isAdmin
is updated, the binding will re-evaluate and add or remove the template as necessary.
There is also an ifnot
binding that just inverts the expression. It's useful if you want to still use a property reference without needing to add a bang and parentheses. The following two lines are equivalent:
<div data-bind="if: !isAdmin()" > <div data-bind="ifnot: isAdmin">
The parentheses are needed in the first example because it is an expression, not a property name. They are not needed in the second example because it is a simple property reference.
The with binding
The with
binding creates a new binding context using the supplied value, which causes bindings inside the bound element to be scoped to the new context. These two snippets are functionally similar:
<div> First Name: <span data-bind="text: selectedPerson().firstName"></span> Last Name: <span data-bind="text: selectedPerson().lastName"></span> </div> <div data-bind="with: selectedPerson"> First Name: <span data-bind="text: firstName"></span> Last Name: <span data-bind="text: lastName"></span> </div>
While saving a few keystrokes and keeping your bindings easier to read is nice, the real benefit of the with
binding is that it is an implicit if
binding. If the value is null
or undefined
, the content of the HTML element will be removed from the DOM. In the cases where this is possible, it saves you from the need to make null checks for each descendant binding.
The foreach binding
The foreach
binding creates an implicit template using the contents of the HTML element and repeats that template for every element in the array.
This viewmodel contains a list of people we need to render:
var viewmodel = { people: [{name: 'Tim'}, {name: 'Justin}, {name: 'Mark'}] }
With this binding, we create an implicit template for the li
element:
<ul data-bind="foreach: people"> <li data-bind="text: name"></li> </ul>
This binding produces the following HTML:
<ul> <li>Tim</li> <li>Justin</li> <li>Mark</li> </ul>
The thing to note here is that the li
element is binding against name
, which is the property of a person. Inside the foreach
binding, the binding context is the child element. If you need to refer to the child itself, you can either use $data
or supply an alias to the foreach
binding.
The $data
option is useful when the array only contains primitives that you want to bind against:
var viewmodel = { people: ['Tim', 'Justin, 'Mark'] } ... <ul data-bind="foreach: people"> <li data-bind="text: $data"></li> </ul>
The alias
option can clean up your code, but it is particularly useful when you have a nested context and want to refer to the parent. Refer to the following code:
<ul data-bind="foreach: { data: categories, as: 'category' }"> <li> <ul data-bind="foreach: { data: items, as: 'item' }"> <li> <span data-bind="text: category.name"></span>: <span data-bind="text: item"></span> </li> </ul> </li> </ul>
This can be achieved with $parent
, of course, but it is more legible when using an alias
.