Knockout templates: afterRender triggered twice because name/data are coupled observables

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 the data computed observable does not appear to cause the template binding to pick up the new object when template.name changes (not sure if this is because the template binding is not actually reevaluating data when template.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

http://jsfiddle.net/mbest/UXYJx/

Leave a Reply

Your email address will not be published. Required fields are marked *