How to Watch for Array Changes

How to watch for array changes?

There are a few options...

1. Override the push method

Going the quick and dirty route, you could override the push() method for your array1:

Object.defineProperty(myArray, "push", {
// hide from for..in and prevent further overrides (via default descriptor values)
value: function () {
for (var i = 0, n = this.length, l = arguments.length; i < l; i++, n++) {
RaiseMyEvent(this, n, this[n] = arguments[i]); // assign/raise your event
}
return n;
}
});

1 Alternatively, if you'd like to target all arrays, you could override Array.prototype.push(). Use caution, though; other code in your environment may not like or expect that kind of modification. Still, if a catch-all sounds appealing, just replace myArray with Array.prototype.

Now, that's just one method and there are lots of ways to change array content. We probably need something more comprehensive...

2. Create a custom observable array

Rather than overriding methods, you could create your own observable array. This particular implementation copies an array into a new array-like object and provides custom push(), pop(), shift(), unshift(), slice(), and splice() methods as well as custom index accessors (provided that the array size is only modified via one of the aforementioned methods or the length property).

function ObservableArray(items) {
var _self = this,
_array = [],
_handlers = {
itemadded: [],
itemremoved: [],
itemset: []
};

function defineIndexProperty(index) {
if (!(index in _self)) {
Object.defineProperty(_self, index, {
configurable: true,
enumerable: true,
get: function() {
return _array[index];
},
set: function(v) {
_array[index] = v;
raiseEvent({
type: "itemset",
index: index,
item: v
});
}
});
}
}

function raiseEvent(event) {
_handlers[event.type].forEach(function(h) {
h.call(_self, event);
});
}

Object.defineProperty(_self, "addEventListener", {
configurable: false,
enumerable: false,
writable: false,
value: function(eventName, handler) {
eventName = ("" + eventName).toLowerCase();
if (!(eventName in _handlers)) throw new Error("Invalid event name.");
if (typeof handler !== "function") throw new Error("Invalid handler.");
_handlers[eventName].push(handler);
}
});

Object.defineProperty(_self, "removeEventListener", {
configurable: false,
enumerable: false,
writable: false,
value: function(eventName, handler) {
eventName = ("" + eventName).toLowerCase();
if (!(eventName in _handlers)) throw new Error("Invalid event name.");
if (typeof handler !== "function") throw new Error("Invalid handler.");
var h = _handlers[eventName];
var ln = h.length;
while (--ln >= 0) {
if (h[ln] === handler) {
h.splice(ln, 1);
}
}
}
});

Object.defineProperty(_self, "push", {
configurable: false,
enumerable: false,
writable: false,
value: function() {
var index;
for (var i = 0, ln = arguments.length; i < ln; i++) {
index = _array.length;
_array.push(arguments[i]);
defineIndexProperty(index);
raiseEvent({
type: "itemadded",
index: index,
item: arguments[i]
});
}
return _array.length;
}
});

Object.defineProperty(_self, "pop", {
configurable: false,
enumerable: false,
writable: false,
value: function() {
if (_array.length > -1) {
var index = _array.length - 1,
item = _array.pop();
delete _self[index];
raiseEvent({
type: "itemremoved",
index: index,
item: item
});
return item;
}
}
});

Object.defineProperty(_self, "unshift", {
configurable: false,
enumerable: false,
writable: false,
value: function() {
for (var i = 0, ln = arguments.length; i < ln; i++) {
_array.splice(i, 0, arguments[i]);
defineIndexProperty(_array.length - 1);
raiseEvent({
type: "itemadded",
index: i,
item: arguments[i]
});
}
for (; i < _array.length; i++) {
raiseEvent({
type: "itemset",
index: i,
item: _array[i]
});
}
return _array.length;
}
});

Object.defineProperty(_self, "shift", {
configurable: false,
enumerable: false,
writable: false,
value: function() {
if (_array.length > -1) {
var item = _array.shift();
delete _self[_array.length];
raiseEvent({
type: "itemremoved",
index: 0,
item: item
});
return item;
}
}
});

Object.defineProperty(_self, "splice", {
configurable: false,
enumerable: false,
writable: false,
value: function(index, howMany /*, element1, element2, ... */ ) {
var removed = [],
item,
pos;

index = index == null ? 0 : index < 0 ? _array.length + index : index;

howMany = howMany == null ? _array.length - index : howMany > 0 ? howMany : 0;

while (howMany--) {
item = _array.splice(index, 1)[0];
removed.push(item);
delete _self[_array.length];
raiseEvent({
type: "itemremoved",
index: index + removed.length - 1,
item: item
});
}

for (var i = 2, ln = arguments.length; i < ln; i++) {
_array.splice(index, 0, arguments[i]);
defineIndexProperty(_array.length - 1);
raiseEvent({
type: "itemadded",
index: index,
item: arguments[i]
});
index++;
}

return removed;
}
});

Object.defineProperty(_self, "length", {
configurable: false,
enumerable: false,
get: function() {
return _array.length;
},
set: function(value) {
var n = Number(value);
var length = _array.length;
if (n % 1 === 0 && n >= 0) {
if (n < length) {
_self.splice(n);
} else if (n > length) {
_self.push.apply(_self, new Array(n - length));
}
} else {
throw new RangeError("Invalid array length");
}
_array.length = n;
return value;
}
});

Object.getOwnPropertyNames(Array.prototype).forEach(function(name) {
if (!(name in _self)) {
Object.defineProperty(_self, name, {
configurable: false,
enumerable: false,
writable: false,
value: Array.prototype[name]
});
}
});

if (items instanceof Array) {
_self.push.apply(_self, items);
}
}

(function testing() {

var x = new ObservableArray(["a", "b", "c", "d"]);

console.log("original array: %o", x.slice());

x.addEventListener("itemadded", function(e) {
console.log("Added %o at index %d.", e.item, e.index);
});

x.addEventListener("itemset", function(e) {
console.log("Set index %d to %o.", e.index, e.item);
});

x.addEventListener("itemremoved", function(e) {
console.log("Removed %o at index %d.", e.item, e.index);
});

console.log("popping and unshifting...");
x.unshift(x.pop());

console.log("updated array: %o", x.slice());

console.log("reversing array...");
console.log("updated array: %o", x.reverse().slice());

console.log("splicing...");
x.splice(1, 2, "x");
console.log("setting index 2...");
x[2] = "foo";

console.log("setting length to 10...");
x.length = 10;
console.log("updated array: %o", x.slice());

console.log("setting length to 2...");
x.length = 2;

console.log("extracting first element via shift()");
x.shift();

console.log("updated array: %o", x.slice());

})();

Watch for current value changes in array

By default in Vue objects (which include arrays) only trigger the watch when created or replaced. In order to watch for mutations of arrays/objects you need to include the deep option (deep: true).

You'll need to slightly modify your watch so you can pass in the deep option - I believe it will end up looking like this:

watch(() => map.value, 
(currentValue) => {
currentValue.circles.forEach((item) => {
console.log(item)
})
},
{deep: true}
);

Sources:

Vue 3 docs about using deep

Vue 3 docs about using watch

Vue 3 Using Deep with watch() & Composition API

Kotlin watch array change

Array's API is quite simple: elements can be written there and can be read from an array.

At 99% (a number without justification, read "the vast majority") array's usages people are satisfied with this simple API. It would be a shame if a simple interface with straightforward implementation was mixed with tricky functionality.

Moving to your problem, a possible approach could be create an array's wrapper

class ArrayWrapper<T> (private val array: Array<out T>, 
private val onChange: () -> Unit) {
val size = array.size

fun get(index: Int): T {
return array[index]
}

fun set(index: Int, value: T) {
array[index] = value
onChange()
}
}

An example of usage:

val ints = ArrayWrapper(arrayOf(1, 2, 3)) {
println("Array has been changed")
}

Array change listener

What I did is I made my own "array" type that just extended the prototype array, which then I added my own handlers to.

For example:

var MyArray = function() {
var arr = [];
arr.push = function() {
console.log("PUSHING", arguments);
return Array.prototype.push.apply(this, arguments);
}

return arr;
};

Usage:

var arr = new MyArray;
arr.push(12, 3, 45);
...

Fiddle: http://jsfiddle.net/maniator/vF659/

How to watch for changes in objects inside javascript array?

Use deep to watch object changes:

watch{
item: {
handler(newValue, oldValue){
// something
},
deep: true
}
}


Related Topics



Leave a reply



Submit