How to create a computed observable array in Knockout

42,075

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(); 
});
Share:
42,075

Related videos on Youtube

Matt Mangold
Author by

Matt Mangold

Updated on January 14, 2020

Comments

  • Matt Mangold
    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
    Eirinn over 10 years
    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.
  • tne
    tne about 10 years
    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.
  • jenny
    jenny about 9 years
    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?
  • tne
    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
    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
    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
    Singularity over 8 years
    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.
  • Gricey
    Gricey about 6 years
    For 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
    srinath samala about 5 years
    when you use such a stratergy self.masterList.push('element') says the list is undefined. This becomes vital when you use dependencies like ko sortable