I’m having trouble getting afterRender to trigger exactly once. I’m seeing that afterRender is triggered twice because I have two observables in my template binding that update at the same time:
<div data-bind="template: { name: template.name(), data: template.data, if: template.data, afterRender: doSomeStuff }"> </div>
This is because an update to name
triggers an update to data
since it is a computed observable whose value depends on name:
this.data = ko.computed({ var viewName = this.name(); switch (viewName) { case "a": return this.modelA(); case "b": return this.modelB(); case "c": return this.modelC(); default: return null; } });
What I’m trying to do is log the time the template is rendered. I’m also concerned about potential performance implications of rendering twice – are those DOM elements within the template being created twice every single time name
changes?
It is possible for data
to change (e.g. the modelA, modelB or modelC object changes) without name
changing and I do want data
changes to trigger afterRender if it is changed by itself. Ideally, I would be able to couple the name-data update to trigger afterRender
once but I’m not sure if this is possible.
Is there an elegant way to accomplish this?
- I looked into throttle extenders but those wouldn’t prevent the template binding from being triggered twice
deferEvaluation
on thedata
computed observable does not appear to cause the template binding to pick up the new object whentemplate.name
changes (not sure if this is because the template binding is not actually reevaluatingdata
whentemplate.name
changes), and should still trigger the template binding twice.
I’m thinking about getting rid of the data object completely and in each HTML template, attaching a with
binding directly to modelA, modelB, and modelC, but then I wouldn’t get those afterRender updates if the data changes.
Edit: I am using KO 2.2.1, and realized that the template name can’t be observable (until 2.3.0), hence the parentheses after template.name. So my original assumption that this afterRender is happening twice because name is an observable too must be incorrect?
Answer
Bindings are updated using a computed observable that tracks changes to any dependencies. Your situation is like this: computed C (template
binding) is dependent on computed B (data
) and observable A (name
), and computed B is dependent on observable A. When A changes, it updates B and C. And when B updates, it also updates C. So that’s why C gets two updates.
For reference, here’s your example in jsFiddle showing two updates: http://jsfiddle.net/mbest/DnY9V/
Throttle extender
Knockout’s throttle extender is the only built-in way to prevent multiple updates. You can create a custom binding that wraps the template binding to run the update in a throttled computed observable.
ko.bindingHandlers.throttledTemplate = { init: function(element) { var args = arguments, self = this, result = ko.bindingHandlers.template.init.apply(this, arguments); ko.computed(function() { ko.bindingHandlers.template.update.apply(self, args); }, null, {disposeWhenNodeIsRemoved: element}).extend({throttle:1}); return result; } };
http://jsfiddle.net/mbest/j5D6p/
Deferred Updates plugin
Alternatively, you can use my Deferred Updates plugin that prevents all duplicate updates to computed observables: https://github.com/mbest/knockout-deferred-updates