83

Given a computed property

vm.checkedValueCount = ko.computed(function(){
  var observables = getCurrentValues();  //an array of ko.observable[]
  return _.filter(observables, function(v) { return v() }).length;
});

suppose getCurrentValues() can return different sets of observables which are modified elsewhere in the code (and comes from a more complex structure than an observableArray).

I need checkedValueCount to update whenever

  • one of its dependencies change
  • getCurrentValues() returns a different set of observables.

The problem is that ko.computed seems to memoize the last returned value and only update when a dependency updates. This handles the first case but not the latter.

What I'm looking for is a way to force checkedValueCount to re-run. Something which I can use like:

changeCurrentValues();
vm.checkeValueCount.recalculate();

Put most simply, given that I have

a = ko.computed(function() { return Math.random() })

how can I force invoking a() twice to return different values.

isherwood
  • 58,414
  • 16
  • 114
  • 157
George Mauer
  • 117,483
  • 131
  • 382
  • 612

5 Answers5

121

I realized my first answer missed your point, and won't solve your issue.

The problem is that a computed will only reevaluate if there is some observable that forces it to re-evaluate. There is no native way to force a computed to re-evaluate.

However, you can get around this with some hackery by creating a dummy observable value and then telling its subscribers that it has changed.

(function() {

    var vm = function() {
        var $this = this;

        $this.dummy = ko.observable();

        $this.curDate = ko.computed(function() {
            $this.dummy();
            return new Date();
        });

        $this.recalcCurDate = function() {
            $this.dummy.notifySubscribers();
        };        
    };

    ko.applyBindings(new vm());

}());​

Here is a Fiddle showing this approach

Josh
  • 44,706
  • 7
  • 102
  • 124
8

There is a method to force recalculation of all observables depending on yours:

getCurrentValues.valueHasMutated()
Community
  • 1
  • 1
Rustam
  • 448
  • 6
  • 10
  • 10
    Computeds do not have this method – George Mauer Nov 10 '14 at 19:41
  • `getCurrentValues(); //an array of ko.observable[]` – Rustam Nov 19 '14 at 13:53
  • Oh I see. You're saying find an observable that is a dependency of the computed and call it's `valueHasMutated` method. This isn't really fundamentally any different from Josh's answer above, is it? In fact it forces you to know what is referenced and to know that those references are observable (and not computed). – George Mauer Nov 19 '14 at 15:31
  • 2
    it's good to mention this function nonetheless as it remains an option for readers, though perhaps someone could update the accepted answer to note cases where this function can be interchanged with notifySubscribers(), and if there is any penalty or advantage either way. – Shaun Wilson Feb 02 '15 at 19:54
4

This answer is conceptually the same as the one @josh gave, but presented as a more generic wrapper. Note: this version is for a 'writeable' computed.

I'm using Typescript so I've included the ts.d definition first. So ignore this first part if not relevant to you.

interface KnockoutStatic
{
    notifyingWritableComputed<T>(options: KnockoutComputedDefine<T>, context ?: any): KnockoutComputed<T>;
}

Notifying-writeable-computed

A wrapper for a writable observable that always causes subscribers to be notified - even if no observables were updated as a result of the write call

Just replace function<T> (options: KnockoutComputedDefine<T>, context) with function(options, context) if you don't use Typescript.

ko.notifyingWritableComputed = function<T> (options: KnockoutComputedDefine<T>, context)
{
    var _notifyTrigger = ko.observable(0);
    var originalRead = options.read;
    var originalWrite = options.write;

    // intercept 'read' function provided in options
    options.read = () =>
    {
        // read the dummy observable, which if updated will 
        // force subscribers to receive the new value
        _notifyTrigger();   
        return originalRead();
    };

    // intercept 'write' function
    options.write = (v) =>
    {
        // run logic provided by user
        originalWrite(v);

        // force reevaluation of the notifyingWritableComputed
        // after we have called the original write logic
        _notifyTrigger(_notifyTrigger() + 1);
    };

    // just create computed as normal with all the standard parameters
    return ko.computed(options, context);
}

The main use case for this is when you are updating something that would not otherwise trigger a change in an observable that is 'visited' by the read function.

For instance I am using LocalStorage to set some values, but there is no change to any observable to trigger re-evaluation.

hasUserClickedFooButton = ko.notifyingWritableComputed(
{
    read: () => 
    {
        return LocalStorageHelper.getBoolValue('hasUserClickedFooButton');
    },
    write: (v) => 
    {
        LocalStorageHelper.setBoolValue('hasUserClickedFooButton', v);        
    }
});

Note that all I needed to change was ko.computed to ko.notifyingWritableComputed and then everything takes care of itself.

When I call hasUserClickedFooButton(true) then the 'dummy' observable is incremented forcing any subscribers (and their subscribers) to get the new value when the value in LocalStorage is updated.

(Note: you may think the notify: 'always' extender is an option here - but that's something different).


There is an additional solution for a computed observable that is only readble:

ko.forcibleComputed = function(readFunc, context, options) {
    var trigger = ko.observable().extend({notify:'always'}),
        target = ko.computed(function() {
            trigger();
            return readFunc.call(context);
        }, null, options);
    target.evaluateImmediate = function() {
        trigger.valueHasMutated();
    };
    return target;
};


myValue.evaluateImmediate();

From @mbest comment https://github.com/knockout/knockout/issues/1019.

Community
  • 1
  • 1
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • What do you mean there's no value? Did it work before switching to this. Are you using deferred updates (the global setting). Just realized that there *might* be a conflict if you expect to be able to update the observable and then immediately use the value. If so try adding ko.tasks.runEarly() to my method. – Simon_Weaver Jun 12 '17 at 18:10
1

suppose getCurrentValues() can return different sets of observables which are modified elsewhere in the code

I assume getCurrentValues() is a function. If you could make it a computed, your checkedValueCount would just magically start working.

Can you make getCurrentValues be a computed instead of a function?

Judah Gabriel Himango
  • 58,906
  • 38
  • 158
  • 212
  • I'm oversimplifying my actual scenario a lot but sure, let's say it can be (which would not be the case if it needed for example to take parameters) - I don't see how that would make any difference. Inside of getCurrentValues() I'm slicing and dicing other data to determine which nodes of a tree view need to be returned. the problem is that the function can return different observable values and I need the computed to pick up on it. Basically exactly what the docs talk about with dynamic dependencies but with the complication of them being added on the fly – George Mauer Dec 07 '12 at 19:37
  • It would make a difference if the "slicing and dicing" was based on observables. For example, let's say your slicing and dicing is the nodes that have .IsChecked() == true. You'd then have a computed called .currentValues() that would evaluate all nodes and return the ones with .IsChecked(). Can the slicing and dicing be done on observable properties? – Judah Gabriel Himango Dec 07 '12 at 19:52
  • I'm really not sure what you're saying Judah,every single invocation can't be a ko observable in my case - but more importantly, I don't see how that could possibly matter, knockout doesn't do any reflection over what closures you've called (I haven't checked but unless javascript has gone completely into lisp-land, this is impossible). All the computed can do is at best track any observables that are called. – George Mauer Dec 07 '12 at 20:20
0

since there is no straight forward way to force update a computed, i have created an observable named toForceComputedUpdate, and i called it within the computed function so the computed will listen to its update, then to force update i call it like this toForceComputedUpdate(Math.random)

yoel neuman
  • 133
  • 7