0
votes

I have a computed observable that makes AJAX calls based on other data (in a computed observable). The resulting data is used to populate part of the UI. Sometimes that part of the UI is hidden and I'd like to avoid the AJAX calls when it's hidden. Right now I have the following, but it updates whenever isVisible becomes true:

this.loadData = ko.computed(function() {
    if (this.isVisible()) {
        this.isProcessing(true);
        var self = this;
        $.when.apply($, ko.utils.arrayMap(this.parent.data.filteredSelectedDatasetLinks(), function(datasetLink) {
            return $.ajax({
                url: datasetLink.getDownloadUrl('.json'),
                success: function(data) {
                    //... do stuff with the data
                }
            });
        }))
        .done(function() {
            self.isProcessing(false);
        });
    }
}, this);

So obviously I need to split this up somehow, but I haven't figured out how to do it. To reiterate, when isVisible is false, no updates should happen. When isVisible is true, updates happen whenever filteredSelectedDatasetLinks changes. When isVisible becomes true, updates happen if filteredSelectedDatasetLinks changed while it was false.

2
Maybe a dirty flag on filteredSelectedDatasetLinks that you check before making the AJAX request and clear after a successful request? - RP Niemeyer
Would the dirty flag be observable? How would you set it--another computed observable or a manual subscription? - Michael Best
If the only dependency is filteredSelectedDatasetLinks itself, then I suppose either a computed or manual subscription would be equivalent. I would make it observable, but if you already access filteredSelectedDatasetLinks in the computed that you listed, then it would trigger regardless (don't need the dirty flag to trigger anything, just use to decide if more work needs to be done) - RP Niemeyer
I just ran across your post on pausing computed observables that's similar in concept to what I'm doing here and also suffers the side effect of re-evaluating the computed when it's resumed. This led me to try to work out a generic pauseable computed that satisfies my conditions. I'll post more in a bit. - Michael Best
I haven't thought much about the pauseable stuff, since throttling was added, but it is an interesting idea to try to encapsulate the logic for what you are after. - RP Niemeyer

2 Answers

0
votes

Presumably you want to call your ajax when the filteredSelectedDatasetLinks is changed (and only if visible?). I think the best way to do this is to make that dependency explicit using the subscribe function... (I have simplified slightly and fixed issue with your final 'this')

this.filteredSelectedDatasetLinks.subscribe(function() {
    if (this.isVisible()) {
        this.isProcessing(true);
        var self = this;
        $.when.apply($, ko.utils.arrayMap(this.filteredSelectedDatasetLinks(), function(datasetLink) {
            return $.ajax({
                url: datasetLink.getDownloadUrl('.json'),
                success: function(data) {
                    //... do stuff with the data
                }
            });
        }))
        .done(function() {
            self.isProcessing(false);
        });
    }
}, this);

The issue with your original attempt is that ko.computed runs the function once and automatically works out which observables it needs to subcribe to. In your case this included the isVisible observable (which is not what you wanted). But making it explicit with the subscribe call directly you no longer have to worry about isVisible firing the callback.

0
votes

Here is what I ended up using based on RP Niemeyer's comments.

this.trackData = ko.computed(function() {
    this.parent.data.filteredSelectedDatasetLinks();    // for notification
    this.isDataDirty(true);
}, this);

this.loadData = ko.computed(function() {
    if (this.isVisible() && this.isDataDirty()) {
        this.isDataDirty(false);
        this.isProcessing(true);
        var self = this;
        $.when.apply($, ko.utils.arrayMap(this.parent.data.filteredSelectedDatasetLinks.peek(), function(datasetLink) {
            return $.ajax({
                url: datasetLink.getDownloadUrl('.json'),
                success: function(data) {
                    //... do stuff with the data
                }
            });
        }))
        .done(function() {
            self.isProcessing(false);
        });
    }
}, this);