Reputation: 4528
Take this code:
var koEvents = new ko.subscribable();
var viewModel = function() {
var self = this;
self.data = ko.observableArray([{
valid: true
}, {
valid: true
}]);
self.isValid = ko.computed(function() {
var isValid = true;
ko.utils.arrayForEach(self.data(), function(item) {
console.log(item.valid);
if (!item.valid) {
isValid = false;
return;
};
});
return isValid;
}, this).subscribe(function(newValue) {
alert("Subscribe called!");
koEvents.notifySubscribers(newValue, "dataChanged");
}.bind(this));
return {
data: self.data,
isValid: self.isValid,
};
}
var vm = new viewModel();
ko.applyBindings(vm, document.getElementById("container"));
vm.data()[0].valid = false;
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<div data-bind="text: isValid ? 'valid': 'invalid'">
</div>
</div>
I have two questions...
self.isValid
not called, when I do this vm.data()[0].valid = false;
?alert("Subscribe called!");
) not called when initially isValid is true and later set to false? I expect this to be called twice in my code.Thanks
Upvotes: 1
Views: 1633
Reputation: 1074148
There are several issues with that code, but the main issue in relation to what you've asked is that changing a non-observable property (valid
) on an object in an observable array is not changing the observable array, just a property on an object within it. So naturally there's no notification. If you want notification, you'll need to watch the valid
property (which in turn means it will have to be observable).
Other issues:
One of the issues with Knockout is that it sometimes unwraps observables/computeds for you, but it doesn't if they're part of an expression — you have to do it (with ()
):
<div data-bind="text: isValid() ? 'valid': 'invalid'"></div>
<!-- ------------------------^^ -->
Your code was testing whether isValid
(not isValid()
) was truthy. Which it always is, because it's a function reference.
KO only does the automatic unwrapping for you when the identifier isn't part of an expression. For instance, this works:
<!-- Works -->
<div data-bind="visible: isValid">...</div>
but this doesn't:
<!-- Doesn't work -->
<div data-bind="visible: !isValid">...</div>
(when isValid
is an observable/computed).
You're setting self.isValid
to the subscription handle, not the computed, because you've overdone your chaining. :-) You need to complete the assignment after the end of the call to computed
, and then subscribe:
self.isValid = ko.computed(function() {
// ...
}, this); // <=== End the assignment here
self.isValid.subscribe(function(newValue) {
// ...
}.bind(this));
You're using self = this
, but then also passing this
to computed
and using bind
with subscribe
. This is harmless, but pointless. One or the other is all you need.
There's no need to create a new, separate object as the return value of your VM constructor; you already have an object (the one created by new
).
Here's an example with those various changes. The valid
property on the first entry is set to false
after 800ms:
var koEvents = new ko.subscribable();
var viewModel = function() {
this.data = ko.observableArray([{
valid: ko.observable(true)
}, {
valid: ko.observable(true)
}]);
this.isValid = ko.computed(function() {
var isValid = true;
ko.utils.arrayForEach(this.data(), function(item) {
console.log(item.valid());
if (!item.valid()) {
isValid = false;
return;
};
});
return isValid;
}, this);
this.isValid.subscribe(function(newValue) {
alert("Subscribe called!");
koEvents.notifySubscribers(newValue, "dataChanged");
});
}
var vm = new viewModel();
ko.applyBindings(vm, document.getElementById("container"));
setTimeout(function() {
vm.data()[0].valid(false);
}, 800);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<div data-bind="text: isValid() ? 'valid' : 'invalid'"></div>
</div>
Upvotes: 3