Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ko.computed tracking changes of the elements of an array

Tags:

knockout.js

Is there a way to create a ko.computed field notified of changes happening to the elements of an array?

My first thought was to use observableArray, but it did not work, because

An observableArray tracks which objects are in the array, not the state of those objects

Still, I'm posting this piece of code to illustrate what I'm trying to do.

HTML:

<div data-bind="foreach:arr">
    <input type="text" value="" data-bind="value: a" />
</div>

<div data-bind="foreach:arr">
    <p>
        Field "a" is changed: <span data-bind="text: aChanged()? 'true': 'false'"></span>
    </p>   
</div>
<p>
    Some "a" field from the array is changed: <span data-bind="text: someAChanged()? 'true': 'false'"></span>
</p>

JavaScript:

function AppViewModel() {
    this.arr = ko.observableArray([new A(), new A()]);
    this.someAChanged = ko.computed(function () {
        var ch = false;
        var arr = this.arr();
        for (var i = 0; i < arr.length; i ++) {
            if (arr[i].aChanged()) {
                ch = true;
                break;
            }
            return ch;
        }
    }, this);    
}

function A() {
    this.a = ko.observable(1);
    this.aChanged = ko.computed(function() { 
        return this.a() != 1;
    }, this);
}

ko.applyBindings(new AppViewModel());

As I have no rights to answer my own questions, I'm posting my idea here. I decided to use "subscribe" function. My solution is to add a "parent" link to the elements of an array. Whenever an observable field gets changed a parent field dependent on its children gets changed too:

function Child () {
    this._parent = null;
    this.observableField = ko.observable("");
    this.observableField.subscribe(function (newVal) {
         if (newVal... && this._parent) {
             this._parent.anotherObservableField(...);
         }
    });
}
Child.prototype._setParent(parent) {...} 
like image 652
Natalia Avatar asked Mar 12 '12 07:03

Natalia


People also ask

What is Ko computed?

ko. computed( evaluator [, targetObject, options] ) — This form supports the most common case of creating a computed observable. evaluator — A function that is used to evaluate the computed observable's current value. targetObject — If given, defines the value of this whenever KO invokes your callback functions.

What is KO in knockout JS?

Reacting to a specific observable event with “ko. when” This advanced technique for working with observables was added in Knockout 3.5. Sometimes, rather than reacting to every change to an observable, you just need to know when the observable arrives at a specific value. This is what ko.when makes easy.

What is Ko ObservableArray?

An observableArray just tracks which objects it holds, and notifies listeners when objects are added or removed.


1 Answers

You'll need to make sure that the someAChanged computed is registering a dependency on each item in your array.

Something like this should work:

function AppViewModel() {
    this.arr = ko.observableArray([new A(), new A()]);
    this.someAChanged = ko.computed(function () {
        var ch = false;
        var changedItem = null;
        var arr = this.arr();

        ko.utils.arrayForEach(this.arr(), function(item){
            var changed = item.changed(); //someAChanged registers a change subscription here

            if(changed && !ch){
                ch = true;
                changedItem = item;
            }
        });

        return changedItem;
    }, this);    
};

Essentially this registers a "change" subscription for each item in your array. Your previous code was stopping at the first one it found, which means none of the other items in your array had "change" subscriptions added. Looping through each item in your array and making it a dependency of your someAChanged computed could be expensive if you have a lot of items in your array.

The code above will also return the first item that changed. I assumed this is what you were wanting. If not, it would be pretty easy to re-work it to return an array of changed items.

like image 157
ericb Avatar answered Jan 03 '23 00:01

ericb