Bindings
Syntax#
<!-- ko if:myObservable --><!-- /ko -->
<i data-bind="if:myObservable"></i>
Remarks#
What a binding is
Essentially a binding or a data binding is a way to link your ViewModels to your Views(templates) and vice versa. KnockoutJS uses two-way data binding, which means changes to your ViewModel influence the View and changes to your View can influence the ViewModel.
Under the hood (short overview)
Bindings are just plugins (scripts) that allow you to solve a particular task. This task is more often than not is changing markup (html) according to your ViewModel.
For example, a text
binding allows you to display text and dynamically change it whenever your ViewModel changes.
KnockoutJS comes with many powerful bindings and lets you extend it with your own custom bindings.
And most importantly bindings are not magical, they work according to a set of rules and anytime you are unsure of what a binding does, what parameters it takes or when it will update the view you can refer to source code of the binding.
Consider the following example of a custom binding:
ko.bindingHandlers.debug = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.computed(function () {
var value = ko.unwrap(valueAccessor());
console.log({
value: value,
viewModel: viewModel,
bindingContext: bindingContext
});
}, null, { disposeWhenNodeIsRemoved: element });
}
};
- A binding has a name -
debug
so you can use as follows:
data-bind="debug: 'foo'"
-
The
init
method is called once when the binding is initiated. The rest of the updates are handled by a anonymous computed which is disposed whenelement
is removed. -
The binding prints to console several things: the passed value in our example this value is
foo
(this value can also be observable sinceko.unwrap
method is used to read it), the current viewModel and bindingContext. -
Whenever the passed value changes the binding will print updated information to console.
-
This binding cannot be used with virtual elements (in html comments), only on real elements, since
ko.virtualElements.allowedBindings.debug
flag is not set to true.
When to use parentheses
Without any additional plugins, KnockoutJS will only have live View updates for properties on the ViewModel that are observable (regular observable
, but also computed
, pureComputed
, observableArray
, etc). An observable would be created like this:
var vm = { name: ko.observable("John") };
In this case, vm.name
is a function with two seperate “modes”:
- Getter:
vm.name()
, without arguments, will get the current value; - Setter:
vm.name("Johnnyboy")
, with an argument, will set a new value.
In the built-in data-bindings you can always use the getter form, and you can sometimes actually omit the parentheses, and the binding will effectively “add” them for you. So these are equivalent:
<p data-bind="text: name"></p> ... will work
<p data-bind="text: name()"></p> ... works too
But this will fail:
<p data-bind="text: 'Hello, ' + name + '!'"></p> ... FAILS!
Because as soon as you want to “do” something before passing a value to a data-binding, including value comparisons, you need to properly “get” the values for all observables, e.g.:
<p data-bind="text: 'Hello, ' + name() + '!'"></p> ... works
See also this Q&A for more details.
If / ifnot
You can use the if
binding to determine whether or not the child elements of the node should be created.
<div class="product-info">
<h2> Product1 </h2>
<img src="../products/product1.jpg"/>
<span data-bind="if:featured">
<span class="featured"></span>
</span>
<span data-bind="ifnot:inStock">
<span class="out-of-stock"></span>
</span>
</div>
<script>
ko.applyBindings({
product: {
featured: ko.observable(true),
inStock: ko.observable(false)
}
});
</script>
The inverse of the if
binding is ifnot
<div data-bind="ifnot: someProperty">...</div>
is equivalent to
<div data-bind="if: !someProperty()">...</div>
Sometimes, you’ll wan’t to control the presence of elements without having to create a container (typically for <li>
elements in a <ul>
or <option>
elements inside a <select>
)
Knockout enables this with containerless control flow syntax based on comment tags like so:
<select>
<option value="0">fixed option</option>
<!-- ko if: featured-->
<option value="1">featured option</option>
<!-- /ko -->
</select>
Foreach
Similar to repeaters used in other languages. This binding will allow you to replicate a block of html for each item in an array.
<div data-bind="foreach:contacts">
<div class="contact">
<h2 data-bind="text:name">
<p data-bind="text:info">
</div>
</div>
<script type="text/javascript">
var contactViewModel = function (data) {
this.name = ko.observable(data.name);
this.info = ko.observable(data.info);
};
ko.applyBindings({
contacts: [
new contactViewModel({name:'Erik', info:'Erik@gmail.com'}),
new contactViewModel({name:'Audrey', info:'Audrey@gmail.com'})
]
});
</script>
Notice that when we are looping through our context becomes the item within the array, in this case an instance of the contactViewModel
. Within a foreach
we also have access to
$parent
- the view model that created this binding$root
- the root view model (could also be parent)$data
- the data at this index of the array$index
- the (observable) zero-based index of the rendered item
With
The with
binding binds the HTML inside the bound node to a separate context:
<div data-bind="with: subViewModel">
<p data-bind="text: title"></p>
</div>
The with
binding may also be used without a container element where a container element may not be appropriate.
<!-- ko with: subViewModel -->
<p data-bind="text: title"></p>
<!-- /ko -->
var vm = {
subViewModel: ko.observable()
};
// Doesn't throw an error on the `text: title`; the `<p>` element
// isn't bound to any context (and even removed from the DOM)
ko.applyBindings(vm);
// Includes the `<p>` element and binds it to our new object
vm.subViewModel({ title: "SubViewModel" });
The with
binding has many similarities to the template
or foreach
bindings.
Visible
The visible
binding will hide an element by applying style="display: none;"
to it when the binding evaluate as falsey.
<input type="text" data-bind="textInput: name"> <span class="error" data-bind="visible: isInvalid">Required!</span>
ko.applyBindings(new ViewModel());
function ViewModel(){
var vm = this;
vm.name = ko.observable("test");
vm.isInvalid = ko.computed(function() {
return vm.name().length == 0;
});
}