My Appmodel consists of an observable array of comments
self.comments=ko.observableArray([]); // Default Value is an empty array
/*
Here comes some code to initially fill the observable array
with items from an JSON Response
*/
Furthermore I have two computeds which should represent the very first comment and the last comment
self.firstComment = ko.computed(function () {
var sorted = self.comments.sort(function (left, right) {
return left.Id() - right.Id();
});
return sorted[0];
});
self.lastComment = ko.computed(function () {
var sorted = self.comments.sort(function (left, right) {
return left.Id() - right.Id();
});
return sorted[sorted.length - 1];
});
This works perfectly on initializing the application (loading the JSON from Server, build up App model...), but when I add a comment to the array, the computeds do not recognize that the number of array items has changed (as I understood it, an observable array is just an observable where the array properties themselves are observed). So when I do:
self.comments.push(aNewCommentObject);
self.lastComment is still bound to the array item, that it was when the app loaded initially.
I have found this blog post how to force computation by introducing a dummy observable, but I don't like the approach. For what purpose is an observableArray used then and how?
Additional Challenge: I would like to keep the observableArray Items sorted under every circumstance (because its a comment feed which should be just sorted chronologically). I tried to do this whith an computed commentsSorted
but also have problems that this does not update when the observableArray has new items, so same problem here. Thats the reason, why I am sorting everytime in firstComment and lastComment.
Try unwrapping the comments to trigger Knockout's dependency tracking:
or (same thing):
Of course you can abstract this into a separate computed.
Since computeds cache their return values (just like every other observable does) you don't need to worry about calling
self.commentsSorted
multiple times. It will re-calculate only when the underlying observable array chances.How can you "keep the observableArray items sorted under every circumstance"? My advice is to not try to sort the original array (I'll describe the pitfalls in more detail below) but to use a computed observable that returns a new, sorted array:
An important thing to understand about the JavaScript Array
sort()
method is that it changes the original array. Knockout'sobservableArray
wraps many of the array functions so they can be used directly on theobservableArray
object. That's why you can usemyObservableArray.sort()
instead of having to domyObservableArray().sort()
. But the two aren't equivalent because Knockout wraps array mutator methods differently, approximately like this:So what's wrong with automatically changing the original
observableArray
?It's hard to grasp what's going on. It's easy to either not get a dependency on the array and thus never update past the initialization, or to not notify of the change and thus break other elements from getting the correct view of the array.
Even if you do set it up correctly, you've now created a circular dependency because you have something that's updated whenever the array changes, which then updates the array again. Although Knockout currently disables this for synchronous updates of a computed observable (but that might change in the future), it will result in unbounded recursion if using a manual subscription or a computed with the
throttle
option.Alternative solution, using
subscribe
: