Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defer evaluation on all computed properties in Knockout?

Tags:

knockout.js

I am trying to make a base class for all my viewmodels so I for instance can check "isDirty" on the root-vm and have it check the entire tree of vms (while avoiding circular dependencies).

When doing this I realized that the order of ko-properties is very important because a computed property set up its subscriptions based on what other properties exists at the time of creating the property. So if I would put a isDirty-computed like the one below in a base-class it would always initialize before the properties it depens on even exists. Anyway, to cut it short, I noticed I could use the deferEvaluation to remedy this problem.

My question are:

Are there any drawbacks of using deferEvaluation on all computed properties? Why is'nt this behaviour true by default? When would I need it set to false?

Can I do this isDirty-property better? Any suggestions on improving it or doing it in some other way?

And alternatively, is there any way to explicitly disable initialization of computed properties until the entire object with all its properties is created, and then run it in some way. I mean the only problem for me is that the subscriptions are set up before all properties are in place.

Note: I'm using KO Lite Tools for dirty-tracking

function ViewModel() {
    var self = this;
    self.isDirty = ko.computed(function () {
        for (var p in self) {
            if (self[p].isDirty) {
                if (self[p].isDirty()) return true;
            }
            else if (self[p].subscribe && self[p].push) { // assuming ko.observableArray
                for (var i = 0, j = self[p]().length; i < j; i++) {
                    if (self[p]()[i].isDirty) {
                        if (self[p]()[i].isDirty()) return true;
                    }
                }
            }
        }
        return false;
    }, this, { deferEvaluation: true });
}

Note: Just noticed that this code will fail if I have another computed property that is depending on isDirty. This was kind of expected but also unfortunate. It would be great if I instead could delay and force all subscriptions to after object creation.

like image 618
Andreas Zita Avatar asked May 01 '13 17:05

Andreas Zita


People also ask

What is deferred in knockout JS?

Deferred – Notifications happen asynchronously, immediately after the current task and generally before any UI redraws. Rate-limited – Notifications happen after the specified period of time (a minimum of 2-10 ms depending on the browser).

Which function is used for computation in knockout?

In some scenarios, it is useful to programmatically determine if you are dealing with a computed observable. Knockout provides a utility function, ko. isComputed to help with this situation. For example, you might want to exclude computed observables from data that you are sending back to the server.

What is Ko observable?

An observable is useful in various scenarios where we are displaying or editing multiple values and require repeated sections of the UI to appear and disappear as items are inserted and deleted. The main advantage of KO is that it updates our UI automatically when the view model changes.


2 Answers

As you found out, normally, a ko.computed is calculated and dependencies are detected when the computed is first created.

When using deferEvaluation, dependency detection for a ko.computed is not performed immediately, but instead is performed each time the value is requested.

This provides a powerful mechanism for dynamically varying the dependencies, but does result in increased overhead.

Ryan talks about this a little on this page (scroll down to section 3 - The basic rules of computed observables):


Would it be possible to structure your code so that the ko.computed values are defined after the other properties are created?

If your main problem is in the dirty flag tracking, you could set up a beginInit() and endInit() methods in your base VM and set up your isDirty computed inside the endInit(). You or may not need the beginInit(), but it provides a nice hook for consistency and may be useful later on.

Of course, the derived VM needs to call the base init methods before and after setting up its observables. I'm not sure what your current strategy for creating the derived VM is - I'm typically use a factory to provide prototypal inheritance, and I have this sort of control over the initialization timing.

An simpler alternative to the beginInit() and endInit(), is to simply provide an initProperties() function in the derived classes where all the properties are defined, and call that method from the base VM, then set up your isDirty computed.

like image 195
Joseph Gabriel Avatar answered Sep 27 '22 19:09

Joseph Gabriel


If your computed is truly a computation, then there is generally no harm in deferring evaluation until someone actually needs the value.

In cases, where your computed actually performs some action either as a by-product of getting a value or as its only purpose, then deferring evaluation may not be the right choice. In some cases, a computed can be used as a way to subscribe to multiple observables and perform an action when any of them change rather than simply returning a value.

There is not a way to disable initialization until the entire object has been created other than by deferring evaluation and accessing it later, unless you were to do something like create your computed in a setTimeout. This could cause errors though, if other code expects it to be there.

like image 32
RP Niemeyer Avatar answered Sep 27 '22 18:09

RP Niemeyer