Note: This plugin isn't necessary with Knockout 3.0, since it includes the behavior added by Knockout Freedom (but using a different method).
Ryan Niemeyer’s blog post “Knockout.js Performance Gotcha #3 - All Bindings Fire Together” very clearly describes how and why sibling bindings (multiple bindings on the same element) all update together in Knockout. I discovered this problem soon after I started developing with Knockout and have come up with a few solutions since then:
A simple wrapper binding isolates a specific binding from its siblings by calling its update
function within a ko.computed
within the wrapper’s init
function. Although this method does work in some cases, it fails in a few ways. The wrapped binding doesn’t get updates from observables that are accessed directly in the binding string. The binding value isn’t accessible by other bindings through allBindingAccessor
because the name of the binding is different. The binding’s run order is changed since the init
functions of all bindings are run before all update
functions.
My first fork of Knockout isolates sibling bindings by running each binding handler in its own ko.computed
context and by preprocessing the binding string so that each binding value is wrapped in a function, which is unwrapped within the handler by the valueAccessor
function. This works even better than the wrapper binding above, but still has some problems. Because the values are unwrapped in the valueAccessor
functions, allBindingAccessor
also has to unwrap the binding values, all of them--thus nullifying any benefit of having them wrapped if a handler uses allBindingAccessor
(solving this requires modifying the binding handler code). Also, this method is incompatible with custom binding providers because it expects all binding values to be wrapped in functions. And it breaks top-level observable view models, which will no longer update any bindings.
My second fork of Knockout is much more ambitious than the first one and includes many other useful features and fixes. It uses a similar fix to the problem as my first fork, with some important changes. The function used to wrap binding values is tagged so that the valueAccessor
functions can work without a wrapped value. This fixes the compatibility issue with custom binding providers and avoids having to wrap all values. Observable view models (at the top or child levels) have specific support in this fork, as well. However, the allBindingAccessor
compatibility issue still exists for the same reasons as above. This fork also has the downside that many will be uncomfortable using it since it includes so many changes to the Knockout code.
This plugin, Knockout-Freedom, is my latest solution to the linked-bindings problem, combining the best solutions from each of my previous attempts. Like my Knockout forks, it preprocesses the binding string to wrap certain values in a function. And like my wrapper binding, it wraps each binding handler in a new version that uses init
to manage updates. It avoids the allBindingAccessor
problem by having the value unwrapped through ko.utils.unwrapObservable
instead of by valueAccessor
. Also, it avoids the binding order problem by wrapping all bindings and solves the naming problem by using the bindings’ original names. There are still a couple of minor issues that this plugin doesn’t address: it breaks observable view models (until they can be implemented in a more robust manner) and it can break updates for a binding handler that doesn’t use unwrapObservable
for binding values (but almost all do).