How to create a computed observable array in Knockout
Asked Answered
M

5

45

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
Mycosis answered 2/7, 2012 at 18:3 Comment(0)
R
33

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);
Rode answered 2/7, 2012 at 18:17 Comment(3)
I 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 or listB 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.Vale
It 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.Alessandro
when you use such a stratergy self.masterList.push('element') says the list is undefined. This becomes vital when you use dependencies like ko sortableHadlock
V
14
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.

Vale answered 17/4, 2014 at 14:54 Comment(4)
This 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?Defect
@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.Vale
@Vale 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.Sob
@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.Vale
V
9

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;
};
Vito answered 9/5, 2013 at 15:1 Comment(0)
H
6

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.

Highball answered 2/7, 2012 at 18:32 Comment(2)
Well, 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.Celloidin
For the benefit of people like me finding this later: this is not true, see the comments on the accepted answer as to whyBrinkmanship
H
3

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(); 
});
Hyperopia answered 13/1, 2017 at 13:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.