Update: much of this is obsolete, if using native templates in KO 2.0
When reading the KnockoutJS documentation regarding observableArrays, one feature that immediately stands out is the ability to have a template rendered only for the changed items in an array. This is accomplished by using the foreach option of the template binding, as opposed to the {{each}}
syntax of the jQuery Templates plugin.
The Problem
Anyone using an observableArray that is frequently manipulated will certainly want to take advantage of the foreach
feature. However, I find it easy to catch your fingers typing something like this:
1 2 3 4 5 6 7 8 9 |
|
Our objective is simple: If there is anything in our items array, then we want to display a section with a header and the name of each item. Whenever an item is added to our array, we want to just render that new item using a template named itemTmpl
. The foreach option of the template binding should take care of this for us, but we have a problem. All of our items are actually getting re-rendered on each change. Try it here.
Dependencies in Templates
In Knockout, bindings are implemented using computed observables to keep track of any dependencies that should trigger the binding to run again. The template binding uses this concept as well. Any observables that are accessed in your template outside of data-bind
attributes will create a dependency.
Our main
template actually has a dependency on the items
observableArray in the {{if}}
statement that checks to see if the array exists and has a length greater than zero. Any change to items
will cause this template to be re-rendered, which will re-render the ul
element that contains our foreach
.
Potential Solutions
To find a potential solution to this issue, we need to be very careful about which observables that we access outside of a data-bind attribute in our template. The easiest solution is to use the visible
binding on our section container instead of wrapping it in an {{if}}
statement. If we know that our observableArray will always be initailized to at least an empty array, then the we won’t need the null check. In many cases, this should be a suitable alternative.
1 2 3 4 5 6 7 |
|
On each change of the items
array, we will now only evaluate the visibility of the container div
rather than re-render the entire template.
In some cases though, the {{if}}
statement may really have been our preferred approach. Maybe we have to do a lot of unnecessary work in the template to generate some elements that will be hidden by the visible binding. What we would really like to do is bind to an observable that only changes when the array crosses between 0 and 1 items.
The easiest way to accomplish this is by using a manual subscription. A computed observable seems like a logical choice, but it would actually get re-evaluated on any change to its dependencies. We only want to trigger a change when the array moves between empty and having items. A manual subscription that controls a separate observable will give us the flexibility to control how it changes.
1 2 3 4 5 6 7 8 9 10 |
|
Now, we can go back to using a template with an {{if}}
statement, but this time it will be bound to the hasItems
observable.
1 2 3 4 5 6 7 |
|
The template rendering is smart enough to recognize that hasItems
by itself is a function and will call it appropriately to retrieve the value of our observable, so we do not need to write hasItems()
. In this case we want our template to get re-rendered whenever hasItems
changes. However, if our main
template had more content, then this might not be so attractive. We might need to break our templates into smaller parts and pass either the data
or foreach
options to the template
binding to render sub-templates.
Test your Templates
For any application that makes significant use of the foreac
option of the template binding, it would be useful to at least do a small amount of testing to ensure that your templates are being rendered as you expect.
One easy way to do this testing is to print out the time that your template is being rendered. The way that I have been doing this is by extending the jQuery Template plugin tags with a now
tag like this:
1 2 3 4 5 |
|
You can use anywhere that you want this information included. Note: if you are using a version of the jQuery Templates plugin prior to 1.0.0pre, then you should use _
instead of __
in the open
tag.
Sample: