knockout.js - knockoutjs - Forzar una función de propiedad calculada para ejecutar
knockoutjs variables (5)
Dada una propiedad calculada
vm.checkedValueCount = ko.computed(function(){
var observables = getCurrentValues(); //an array of ko.observable[]
return _.filter(observables, function(v) { return v() }).length;
});
supongamos que getCurrentValues () puede devolver diferentes conjuntos de observables que se modifican en otras partes del código (y proviene de una estructura más compleja que un array observable).
Necesito checkedValueCount
para actualizar cada vez
- una de sus dependencias cambia
- getCurrentValues () devuelve un conjunto diferente de observables.
El problema es que ko.computed
parece memorizar el último valor devuelto y solo se actualiza cuando se actualiza una dependencia. Esto maneja el primer caso pero no el segundo.
Lo que estoy buscando es una forma de forzar checkValueCount para volver a ejecutar. Algo que puedo usar como:
changeCurrentValues();
vm.checkeValueCount.recalculate();
En pocas palabras, dado que tengo
a = ko.computed(function() { return Math.random() })
¿Cómo puedo forzar invocar a()
dos veces para devolver diferentes valores?
supongamos que getCurrentValues () puede devolver diferentes conjuntos de observables que se modifican en otro lugar en el código
Supongo que getCurrentValues () es una función. Si pudieras calcularlo, tu valor de CheckValue simplemente comenzaría a funcionar mágicamente.
¿Se puede hacer que getCurrentValues sea un cálculo en lugar de una función?
Esta respuesta es conceptualmente la misma que la que dio @josh, pero presentada como una envoltura más genérica. Nota: esta versión es para un computable ''escribible''.
Estoy usando Typescript, así que he incluido la definición de ts.d primero. Así que ignora esta primera parte si no es relevante para ti.
interface KnockoutStatic
{
notifyingWritableComputed<T>(options: KnockoutComputedDefine<T>, context ?: any): KnockoutComputed<T>;
}
Notificando-escribible-computado
Un contenedor para un observable
escribible que siempre causa que se notifique a los suscriptores, incluso si no se actualizaron observables como resultado de la llamada de write
Simplemente reemplace la function<T> (options: KnockoutComputedDefine<T>, context)
con la function(options, context)
si no usa 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);
}
El caso de uso principal para esto es cuando está actualizando algo que de otra manera no provocaría un cambio en un observable que es ''visitado'' por la función de read
.
Por ejemplo, estoy usando LocalStorage para establecer algunos valores, pero no hay ningún cambio en cualquier observable para activar la reevaluación.
hasUserClickedFooButton = ko.notifyingWritableComputed(
{
read: () =>
{
return LocalStorageHelper.getBoolValue(''hasUserClickedFooButton'');
},
write: (v) =>
{
LocalStorageHelper.setBoolValue(''hasUserClickedFooButton'', v);
}
});
Tenga en cuenta que todo lo que necesitaba para cambiar fue ko.computed
a ko.notifyingWritableComputed
y luego todo se ko.notifyingWritableComputed
solo.
Cuando llamo hasUserClickedFooButton(true)
, el observable "ficticio" se incrementa forzando a los suscriptores (y sus suscriptores) a obtener el nuevo valor cuando se actualiza el valor en LocalStorage.
(Nota: puede pensar que notify: ''always''
extensor notify: ''always''
es una opción aquí, pero eso es algo diferente).
Hay una solución adicional para un observable calculado que solo se puede leer:
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();
Desde @mbest comment https://github.com/knockout/knockout/issues/1019 .
Hay un método para forzar el recálculo de todos los observables que dependen del suyo:
getCurrentValues.valueHasMutated()
Me di cuenta de que mi primera respuesta erró su punto, y no resolverá su problema.
El problema es que un cálculo solo volverá a evaluar si hay algún observable que lo obligue a reevaluar. No hay una forma nativa de forzar un cálculo para volver a evaluar.
Sin embargo, puede evitar esto con algunos hackers creando un valor ficticio observable y luego diciéndole a sus suscriptores que ha cambiado.
(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());
}());
Aquí hay un violín que muestra este enfoque
dado que no hay una forma directa de forzar la actualización de un computed, he creado un elemento visible llamado toForceComputedUpdate, y lo llamé dentro de la función calculada para que el computador escuche su actualización, luego para forzar la actualización lo llamo así aForceComputedUpdate (Math .aleatorio)