Under certain circumstances a ko.computed
will not update even though the ko.observable
that it is bound to changes. I would like to know why, what I am doing wrong and what I should be doing instead.
Example
Consider this simple dozen-to-pieces-converter (JSFiddle here).
HTML
<label>Dozen:</label><br>
<input type="number" data-bind="value: dozen">
<span class="error" data-bind="text: error"></span><br>
<label>Pieces:</label><br>
<input type="number" data-bind="value: pieces"><br>
JavaScript
function ViewModel() {
var self = this;
self.error = ko.observable('');
self.pieces = ko.observable('');
self.dozen = ko.computed({
read: function() {
var p = parseInt(self.pieces(), 10);
if (!p) return '';
if (p % 12 === 0) return p / 12;
else return '';
},
write: function(value) {
if (/\D/.test(value)) {
self.error('Only whole numbers');
} else {
self.error('');
if (value) self.pieces(value * 12);
}
}
});
}
ko.applyBindings(new ViewModel());
What it does
It will display two inputs. One lets you enter dozens (for example 2) and the other will display how many pieces that is (24).
You can also input a number of pieces (for example 36) and the the converter will show how many dozens that is (3).
If you enter something in the pieces
box that isn't divisible by 12, the dozens
input is cleared indicating that the number is not an even dozen. Likewise, we don't allow the user to enter fractional dozens.
How it works
The dozens
input is bound to a KnockoutJS computed observable. It does not hold it's own value (maybe it does under the hood, but this is beyond my knowledge) but is computed from the pieces
property, which is a regular observable. To get the dozens, the pieces is divided by 12 and if the result is a whole number, that is returned, otherwise we return an empty string.
The problem
Start the calculator and try typing 1.5
in the dozens
input. The calculator will inform you that decimals are not allowed. The pieces
field will be left untouched, as it should. However, the dozens
input is not cleared to reflect the value of the pieces
field.
Then enter a new value in the pieces
input, for example 18
. This should clear the dozens
input (as it is a computed
depending only on the pieces
property of the view model, and this value has changed), but it does not.
The value 1.5
keeps being displayed in the dozens
field even though the read()
function of the computed
will never return a decimal like that. The observable
that the computed
is bound to has been updated, but the computed
does not compute.
Only when you enter a number divisible by 12 in the pieces
field, will the dozens
field update.
My questions
Why does the
dozens
input keep displaying1.5
even though the return value from theread()
function is an empty string? Can I force a newread()
inside or after thewrite()
or otherwise force an update of the UI?I'm assuming that the reason the
dozens
input doesn't update even after changing the value of thepieces
input, is that the result from theread()
function will be the empty string twice in a row and that KnockoutJS caches this value internally and sees that it hasn't changed. Again, how do I force the input to update?