Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Lazy Loading an Observable in KnockoutJS

| Comments

When using Knockout, often it is not efficient to populate the entire view model when a page loads. Typically, this means triggering AJAX requests to retrieve additional data based on changes to the state of the view model. The pattern that I usually use for this process is to create a manual subscription to an observable that represents the currently selected item/category/tab. When the observable changes, I make a request to fill out the rest of its data in the subscription.

I thought that it would be nice to encapsulate some of this logic into a reusable object that does not actually retrieve its data until it is accessed (typically this would mean when it is actually bound). This can be accomplished pretty easily with an augmented computed observable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//an observable that retrieves its value when first bound
ko.onDemandObservable = function(callback, target) {
    var _value = ko.observable();  //private observable

    var result = ko.computed({
        read: function() {
            //if it has not been loaded, execute the supplied function
            if (!result.loaded()) {
                callback.call(target);
            }
            //always return the current value
            return _value();
        },
        write: function(newValue) {
            //indicate that the value is now loaded and set it
            result.loaded(true);
            _value(newValue);
        },
        deferEvaluation: true  //do not evaluate immediately when created
    });

    //expose the current state, which can be bound against
    result.loaded = ko.observable();
    //load it again
    result.refresh = function() {
        result.loaded(false);
    };

    return result;
};

Nothing fancy or complicated at all about this code. A few notes on it:

  • The key is passing the little-known flag, deferEvaluation: true, to the computed observable. This signals it to not do an evaluation until someone requests its value. So, our AJAX request will not go out until it is bound.
  • When you define this object, you pass in a function that will retrieve the data and a target to define the execution context. In this implementation, it is expected that the callback sets the observable asynchronously. Otherwise, our computed observable would depend on itself and potentially try to evaluate itself recursively. The callback could certainly be executed in a setTimeout, but it didn’t seem necessary for how I use it.
  • You can set, read, and bind to this object, just like any observable. It uses a writeable computed observable in front of the actual observable to allow greater control over the reads and writes.
  • Additionally, it exposes a sub-observable named loaded that you can bind against in your UI. You could use this to indicate that something is currently loading or to display a refresh button after it has been loaded.
  • There is also a refresh method that simply sets the loaded flag to false, which causes the dependentObservable to be re-evaluated.
  • You can also manually set the value of this observable, which will automatically set loaded to true. In my sample, the content of the first tab is provided when the page is loaded, so I do not want to retrieve it via AJAX.

Here is a sample scenario that uses this technique:

Link to sample on jsFiddle.net

Here is an additional sample that uses bootstrap tabs:

Link to sample on jsFiddle.net

Comments