How to create a computed observable array in Knockout
Solution 1
This will combine the two arrays and return the combined list. However, it is not a computed observable array (don't know if that is even possible), but a regular computed observable.
self.masterList = ko.computed(function() {
return this.listA().concat(this.listB());
}, this);
Solution 2
self.masterList = ko.observableArray();
ko.computed(function () {
self.masterList(self.listA().concat(self.listB()));
});
Similar to Joe Flateau's answer in spirit, but I like to think this method is simpler.
Solution 3
I know this is an old question but I thought I'd throw my answer in there:
var u = ko.utils.unwrapObservable;
ko.observableArray.fn.filter = function (predicate) {
var target = this;
var computed = ko.computed(function () {
return ko.utils.arrayFilter(target(), predicate);
});
var observableArray = new ko.observableArray(u(computed));
computed.subscribe(function (newValue) { observableArray(newValue); });
return observableArray;
};
Solution 4
An observableArray is just an observable with a few more properties. Therefore, a computed observable that returns an array in the closure will be treated as an array.
Solution 5
I'm not sure if this is the most efficient option - but it is fairly simple and works for me. The ko.computed returns an observable array as below:
self.computedArrayValue = ko.computed(function() {
var all = ko.observableArray([]);
....
return all();
});
A working example of the code: Html:
<div data-bind="foreach: days">
<button class="btn btn-default btn-lg day" data-bind="text: $data, click: $root.dayPressed"></button>
</div>
Javascript function on the view model:
self.days = ko.computed(function() {
var all = ko.observableArray([]);
var month = self.selectedMonth(); //observable
var year = self.selectedYear(); //observable
for (var i = 1; i < 29; i++) {
all.push(i);
}
if (month == "Feb" && year % 4 == 0) {
all.push(29);
} else if (["Jan","Mar","May","Jul","Aug","Oct","Dec"].find((p) => p == month)) {
[29,30,31].forEach((i) => all.push(i));
} else if (month != "Feb") {
[29,30].forEach((i) => all.push(i));
}
return all();
});
Related videos on Youtube
Matt Mangold
Updated on January 14, 2020Comments
-
Matt Mangold over 4 years
I would like to know how to create a computed observable array.
In my view model, I have 2 observable arrays, and I would like to have a computed observable array that is simply both arrays combined.
function ViewModel() { var self = this; self.listA= ko.observableArray([]); self.listB = ko.observableArray([]); self.masterList= //combine both list A and B
-
Eirinn over 10 yearsWell, sort of. I just tested it and it seems that unless it's declared as an observable array, methods like shift and pop are not broken out for you.
-
tne about 10 yearsI believe this answer is flawed for most use-cases: the value of the computed observable is a regular array, not an observable array (roughly stated in the answer). Hence, updating
listA
orlistB
will entirely replace the array itself instead of updating its contents (which is what we want in 99% of the cases). This means that you should not bind views to this observable. In effect, this code is as useful as its non-computed variant. See other answers for different approaches. -
jenny about 9 yearsThis is how I was going to do it as well, but doesn't this still suffer from the issue as the accepted answer; in that any change will cause any view bound to the
masterList
to be redrawn completely? -
tne about 9 years@AdamLewis: Yes, this indeed rebuilds the entire array, and depending on the view engine it may or may not re-render the entire DOM subgraph for whatever views are bound to it (not necessarily though, might just make a diff and apply it). Note that it might still be the best solution to avoid many updates. This is not the issue I outlined regarding the answer you mention though, where if the view engine captures the array property itself (as opposed to the path to it) then it would never detect that you swapped the array (since it's not observable) and thus would never update at all.
-
Nateous about 9 years@tne I'm using this answer for now, but it seems really slow... maybe i'm doing something else wrong. however, is this solution breaking the intended purpose of KO? is there another way to solve the problem of needing a computed observable array? just wondering if this is the right tool to use in my (or any) situation.
-
tne about 9 years@Nate There's nothing wrong with the approach as long as you do need to combine the lists in this particular order. I only used
concat
here because everybody else was, to compare easily with other answers. If I were to remove that requirement, I would probably do things differently (because indeed, this is slow). E.g. push/splice elements directly. If elements change often then I'd probably use a linked list instead of an array. Maybe you can ask a separate question with your requirements. -
Singularity over 8 yearsIt won't work in this case, but the knockout plugin knockout-projections implements far more efficient computed observable arrays using the newish array change subscriptions. This plugin could be extended to support an efficient concat operation.
-
Gricey about 6 yearsFor the benefit of people like me finding this later: this is not true, see the comments on the accepted answer as to why
-
srinath samala about 5 yearswhen you use such a stratergy self.masterList.push('element') says the list is undefined. This becomes vital when you use dependencies like ko sortable