Recently, I worked with several people on questions related to binding multiple view models in a single page. One common approach is to bind a view model to a particular root element using a call like ko.applyBindings(vm, containerNode);. However, a limitation with this approach is that when binding multiple view models, none of the container elements can overlap. This means that you could not bind one view model nested inside of another.
One way to address this issue is to create a top level view model that contains your “sub” view models and then call ko.applyBindings on the entire page with the overall view model:
This technique is nice, because you only have to make a single ko.applyBindings call and you can use $root or $parent/$parents to access data at any time from another view model. However, based on a desire to maintain modular code and to control how and when elements are bound, it is often not convenient or practical to build a top level view model.
With Knockout 2.0, there is a simple alternative that can provide for greater flexibility. Bindings are now able to return a flag called controlsDescendantBindings in their init function to indicate that the current binding loop should not try to bind this element’s children. This flag is used by the template and control-flow bindings (wrappers to the template binding), as they will handle binding their own children with an appropriate data context.
For our scenario, we can take advantage of this flag and simply tell Knockout to leave a certain section alone by using a simple custom binding:
Adding the extra div to hold our stopBinding binding may not cause our app any problems, but if it does then in KO 2.1 we can now create containerless custom bindings by adding our binding to ko.virtualElements.allowedBindings.
Knockout.JS2.1 is now available! Minified and debug versions available here. This release focused mainly on performance improvements and bug fixes after the 2.0 release.
Computed observables no longer can recursively evaluate themselves (I think that has happened to all of us a few times!)
$index is available in foreach scenarios from the binding context. There is no longer a need for workarounds like storing an index as a property of your data items. Note that $index is an observable, so it needs to be reference using $index() in expressions.
$parentContext is available from the binding context as well. While, $parent contains the actual data from one scope level up, $parentContext contains the actual binding context ($data, $parent, $parents, $root, etc.) of the parent.
ko.isComputed is a helper function that was added to determine if a value is a computed observable or not.
ko.toJSON now passes its arguments on to JSON.stringify after calling ko.toJS. This makes creating a handy “debug” section even easier <pre data-bind="text: ko.toJSON($root, null, 2)"></pre> and this can be used to include only certain properties in the converted JSON like ko.toJSON(person, ['firstName', 'lastName']). This seems even more flexible in many cases than the approach that I mentioned here.
Better support for using a single instance of KO between documents - you can now pass an instance of KO between documents and have bindings work properly across the documents. Previously the computed observables associated with bindings would dispose themselves when outside of the original document as they believed they were no longer part of the DOM.
Numerous other bug and performance fixes
For this release Michael Best joined the core team and drove the majority of the changes with Steve. Michael has numerous other exciting performance enhancements in his fork that will be evaluated for possible inclusion (in a non-breaking fashion) in the core. He has really helped tremendously in moving the project forward.
I just finished the process of moving this blog from Blogger to an Octopress site that uses Jekyll to generate a static blog. I am excited to have better control over the look and feel of the blog and to move to a git-based workflow using markdown files for posts.
Here are a few of the steps that I took to make this move:
Imported old comments using the import tool from disqus
Imported blog posts into Octopress using the script here to at least get some basic structure and permalinks.
At that point, I scrapped all of the HTML for each post and recreated each in markdown. It was a painful and time consuming process, but the posts will hopefully now look consistent and will be easier to edit/update in the future.
Made updates/change to the layout and look of the default template.
Created an app in Heroku, pushed the site to it, and added the custom domain.
Pointed the domain to Heroku.
Updated the Feedburner feed to use the new RSS.
I was never too excited about the look, feel, and process of updating the old site. I was mostly focused on getting content out there and a Blogger blog was an easy way to get started. I am excited to continue tweaking the site and using git and markdown for the posts. Hopefully, I will find it easier to make more frequent posts and to update older posts as Knockout changes.
Most Knockout.js applications tend to deal with collections of objects that are stored in one or more observableArrays. When dealing with observableArrays, it is helpful to understand exactly how they work to make sure that you don’t hit a performance issue.
An observableArray is simply an observable with some additional methods added to deal with common array manipulation tasks. These functions get a reference to the underlying array, perform an action on it, then notify subscribers that the array has changed (in KO 2.0, there is now an additional notification prior to the change as well).
For example, doing a push on an observableArray essentially does this:
Now, say we want to add additional data from the server to the observableArray of items. It is easy enough to loop through the new data and push each mapped item to our observableArray.
Consider what happens though each time that we call .push(). The item is added to our underlying array and any subscribers are notified of the change. Each time that we push, our highPriorityItems filter code will run again. Additionally, if we are binding our UI to the items observableArray, then the template binding has to do work each time to determine that only the one new item was added.
A better pattern is to get a reference to our underlying array, push to it, then call .valueHasMutated(). Now, our subscribers will only receive one notification indicating that the array has changed.
Clearing or replacing the contents of an observableArray
Another common scenario is when you want to completely replace the contents of an observableArray. This may be to empty the array or to set it to a new set of items. There is no need to loop through the array and operate on it. The most efficient operation is to simply set it to a new value:
12345
//clear the observableArraythis.items([]);//replace the contents of the observableArraythis.otherItems(newData);
Minimizing the amount of notifications to subscribers from observableArrays is an easy way to avoid a potential performance issue. Typically, this can be done by performing your operations on the underling array and then triggering notifications. In other cases, replacing the observableArray’s value with a completely new value is a good choice that results in a single notification.
update: Knockout 2.2 has addressed the first issue in this post. Now the if and ifnot binding will not re-render their contents unless the value changes between truthy and falsy.
A recent StackOverflow question prompted me to start a series of short posts with some basic performance tips/gotchas for Knockout.js. In smaller applications, many of these items would go unnoticed, but as a project becomes more complex, it is important to understand the amount of work that Knockout is doing to manage your UI.
The control flow bindings introduced in Knockout 2.0 are really just simple wrappers to the template binding. They use the children of the element as their “template” (what we call anonymous templates) and will re-render the contents whenever triggered. If you are not careful, the if and with bindings may be causing re-renders much more often than you might realize.
Problem: if binding causing frequent re-renders
A common scenario where you might use the if binding is when you want to display a section only when an observableArray actually contains items. Something like:
The problem in this case, is that the if binding depends on the items observableArray and re-evaluates every time that it is updated. Each time that it is triggered and has a truthy value, it will actually re-render its “template” again, as you can see here.
Solutions
One choice to avoid these re-renders would be to instead use the visible binding on a container element around our section or on the individual elements, as shown here.
If we prefer to use the if binding in this case, then we need to make sure that it is only triggered when the number of items in our array moves between 0 and 1. One might think that a computed observable holding the length would be a good choice. However, one important thing to note is that currently computed observables will always notify subscribers when re-evaluated, even if their value ends up being the same.
However, this is not true for observables. Notifications are suppressed when you write a value that is identical to the previous value (this behavior can be overridden). So, one option is to subscribe to changes to our observableArray and populate an observable based on the length.
Now, we can bind against hasItems and it will only notify subscribers when changing between true and false based on the length of the array. You can here see that each item is no longer re-rendered on every add.
You could even extend observableArrays to add a function that would attach this hasItems property as a sub-observable and create the subscription to keep it updated. This helps to keep the view model a bit cleaner and allows you to easily reuse the code.
It might look like:
123456789101112131415
ko.observableArray.fn.trackHasItems=function(){//create a sub-observablethis.hasItems=ko.observable();//update it when the observableArray is updatedthis.subscribe(function(newValue){this.hasItems(newValue&&newValue.length?true:false);},this);//trigger change to initialize the valuethis.valueHasMutated();//support chaining by returning the arrayreturnthis;};
Problem: with binding causes entire area to re-render
The with binding is very handy for binding against nested objects/models and helps to keep your data-bind attributes from becoming too verbose. It is important, however, to understand that the with binding will re-render the entire section whenever its bound value changes.
For example, we might want to render a selected item like:
The children of the main div become our template. Whenever selectedItem changes, the contents are re-rendered completely. So, rather than just binding the inputs against the new data, the entire section is replaced with new elements, as shown here. In this case, it won’t cause any issues, but if your markup is complex and/or has events attached outside of Knockout, then you can run into issues when the elements are removed and replaced by the original template.
Solutions
If performance does end up becoming an issue, then we could consider just binding directly to the observables on the selected item. If the selected item might be null, then you would need to make sure that the binding does not fail looking for properties on a null object, as show in this sample.
Another choice would be to use the with binding in smaller, targeted areas. We can make use of the containerless control-flow bindings to wrap more specific sets of elements, as shown here.
Finally, for cases where you want to bind against a nested object that won’t change (non-observable), take a look at the simple withlight binding developed by Michael Best. This will switch the context to your nested object on initialization, but is not designed to bind against an observable that will be changed.
Conclusion
The control-flow bindings are certainly handy, but when performance matters, it helps to understand a little bit about how they are implemented. In cases where you want to avoid re-rendering entire sections of your UI, it takes a little extra work to ensure that this is not happening too frequently.