How dependency tracking works
Beginners don’t need to know about this, but more advanced developers will want to know why we keep making all these claims about KO automatically tracking dependencies and updating the right parts of the UI…
It’s actually very simple and rather lovely. The tracking algorithm goes like this:
- Whenever you declare a computed observable, KO immediately invokes its evaluator function to get its initial value.
- While the evaluator function is running, KO sets up a subscription to any observables (including other computed observables) that the evaluator reads. The subscription callback is set to cause the evaluator to run again, looping the whole process back to step 1 (disposing of any old subscriptions that no longer apply).
- KO notifies any subscribers about the new value of your computed observable.
So, Knockout doesn’t just detect dependencies the first time the evaluator runs - it redetects them every time. This means, for example, that the dependencies can vary dynamically: dependency A could determine whether the computed observable also depend on B or C. Then, it will only be re-evaluated when either A or your current choice of B or C changes. You don’t have to declare dependencies: they’re determined at runtime from the code’s execution. If the evaluator doesn’t access any obsevables, the computed observable will have no dependencies and won’t ever need to call the evaluator function again. In that case, to save resources, the computed observable will be automatically “disposed.”
The other neat trick is that declarative bindings are simply implemented as computed observables. So, if a binding reads the value of an observable, that binding becomes dependent on that observable, which causes that binding to be re-evaluated if the observable changes.
Pure computed observables work slightly differently. For more details, see the documentation for pure computed observables.
Controlling dependencies using peek
Knockout’s automatic dependency tracking normally does exactly what you want. But you might sometimes need to control which observables will update your computed observable, especially if the computed observable performs some sort of action, such as making an Ajax request. The peek
function lets you access an observable or computed observable without creating a dependency.
In the example below, a computed observable is used to reload an observable named currentPageData
using Ajax with data from two other observable properties. The computed observable will update whenever pageIndex
changes, but it ignores changes to selectedItem
because it is accessed using peek
. In this case, the user might want to use the current value of selectedItem
only for tracking purposes when a new set of data is loaded.
ko.computed(function() {
var params = {
page: this.pageIndex(),
selected: this.selectedItem.peek()
};
$.getJSON('/Some/Json/Service', params, this.currentPageData);
}, this);
Note: If you just want to prevent a computed observable from updating too often, see the rateLimit
extender.
Ignoring dependencies within a computed
The ko.ignoreDependencies
function is available for scenarios where you want to execute code within a computed that should not contribute to that computed’s dependencies. This is often useful in a custom binding when you want to call code that may access observables, but you do not want to re-trigger the binding based on changes to those observables.
ko.ignoreDependencies( callback, callbackTarget, callbackArgs );
Example:
ko.bindingHandlers.myBinding = {
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var options = ko.unwrap(valueAccessor());
var value = ko.unwrap(options.value);
var afterUpdateHandler = options.afterUpdate;
// the developer supplied a function to call when this binding updates, but
// we don't really want to track any dependencies that would re-trigger this binding
if (typeof afterUpdateHandler === "function") {
ko.ignoreDependencies(afterUpdateHandler, viewModel, [value, color]);
}
$(element).somePlugin("value", value);
}
}
Note: Why circular dependencies aren’t meaningful
Computed observables are supposed to map a set of observable inputs into a single observable output. As such, it doesn’t make sense to include cycles in your dependency chains. Cycles would not be analogous to recursion; they would be analogous to having two spreadsheet cells that are computed as functions of each other. It would lead to an infinite evaluation loop.
So what does Knockout do if you have a cycle in your dependency graph? It avoids infinite loops by enforcing the following rule: Knockout will not restart evaluation of a computed while it is already evaluating. This is very unlikely to affect your code. It’s relevant in two situations: when two computed observables are dependent on each other (possible only if one or both use the deferEvaluation
option), or when a computed observable writes to another observable on which it has a dependency (either directly or via a dependency chain). If you need to use one of these patterns and want to entirely avoid the circular dependency, you can use the peek
function described above.