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) {...}
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.
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.
An observableArray just tracks which objects it holds, and notifies listeners when objects are added or removed.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With