问题
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.
回答1:
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
回答2:
There is a method to force recalculation of all observables depending on yours:
getCurrentValues.valueHasMutated()
回答3:
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.
回答4:
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?
回答5:
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)
来源:https://stackoverflow.com/questions/13769481/force-a-computed-property-function-to-run