object-observer
object-observer
provides a deep observation of a changes performed on an object/array graph.
Main aspects and features:
- implemented via native Proxy (revokable)
- observation is 'deep', yielding changes from a sub-graphs too
- nested objects of the observable graph are observables too
- changes delivered in a synchronous way by default, asynchronous delivery is optionally available as per
Observable
configuration; more details here - observed path may optionally be filtered as per
observer
configuration; more details here - original objects are cloned while turned into
Observable
s
- circular references are nullified in the clone
- array specifics:
- generic object-like mutations supported
- intrinsic
Array
mutation methods supported: pop
, push
, shift
, unshift
, reverse
, sort
, fill
, splice
, copyWithin
- massive mutations delivered in a single callback, usually having an array of an atomic changes
- typed array specifics:
- generic object-like mutations supported
- intrinsic
TypedArray
mutation methods supported: reverse
, sort
, fill
, set
, copyWithin
- massive mutations delivered in a single callback, usually having an array of an atomic changes
- intrinsic mutation methods of
Map
, WeakMap
, Set
, WeakSet
(set
, delete
) etc are not observed (see this issue for more details) - following host objects (and their extensions) are skipped from cloning / turning into observables:
Date
Supported:
71+ |
65+ |
79+ |
12.1 |
12.0.0+
Performance report can be found here.
Changelog is here.
Preview
For a preview/playground you are welcome to:
- CodePen -
Observable.from()
flavor - CodePen -
new ObjectObserver()
flavor
Install
Use regular npm install @gullerya/object-observer --save-prod
to use the library from your local environment.
ES module:
import { Observable } from '@gullerya/object-observer';
CJS flavor:
const { Observable } = require('@gullerya/object-observer');
Huge thanks to seidelmartin providing the CJS build while greatly improving the build code overall along the way!
CDN (most suggested, when possible):
import { Observable } from 'https://libs.gullerya.com/object-observer/x.y.z/object-observer.min.js';
Replace the x.y.z
with the desired version, one of the listed in the changelog.
CDN features:
- security:
- HTTPS only
- intergrity checksums for SRI
- performance
- highly available (with many geo spread edges)
- agressive caching setup
Full details about CDN usage and example are found here.
API
Library implements Observable
API as it is defined here.
There is also a 'DOM-like' API flavor - constructable ObjectObserver
.
This API is resonating with DOM's MutationObserver
, ResizeObserver
etc from the syntax perspective.
Under the hood it uses the same Observable
mechanics.
Read docs about this API flavor here.
object-observer
is cross-instance operable.
Observables created by different instances of the library will still be detected correctly as such and handled correctly by any of the instances.
Security
Security policy is described here. If/when any concern raised, please follow the process.
Examples
Objects
const
order = { type: 'book', pid: 102, ammount: 5, remark: 'remove me' },
observableOrder = Observable.from(order);
Observable.observe(observableOrder, changes => {
changes.forEach(change => {
console.log(change);
});
});
observableOrder.ammount = 7;
observableOrder.address = {
street: 'Str 75',
apt: 29
};
observableOrder.address.apt = 30;
delete observableOrder.remark;
Object.assign(observableOrder, { amount: 1, remark: 'less is more' }, { async: true });
Arrays
let a = [ 1, 2, 3, 4, 5 ],
observableA = Observable.from(a);
Observable.observe(observableA, changes => {
changes.forEach(change => {
console.log(change);
});
});
observableA.pop();
observableA.push('a', 'b');
observableA.shift();
observableA.unshift('x', 'y');
observableA.reverse();
observableA.sort();
observableA.fill(0, 0, 1);
observableA.splice(0, 1, 'x', 'y');
let customer = { orders: [ ... ] },
oCustomer = Observable.from(customer);
oCustomer.orders.sort();
oCustomer.orders.reverse();
Arrays notes: Some of array operations are effectively moving/reindexing the whole array (shift, unshift, splice, reverse, sort).
In cases of massive changes touching presumably the whole array I took a pessimistic approach with a special non-detailed events: 'reverse' for reverse
, 'shuffle' for sort
. The rest of these methods I'm handling in an optimistic way delivering the changes that are directly related to the method invocation, while leaving out the implicit outcomes like reindexing of the rest of the Array.
Observation options
object-observer
allows to filter the events delivered to each callback/listener by an optional configuration object passed to the observe
API.
In the examples below assume that callback = changes => {...}
.
let user = {
firstName: 'Aya',
lastName: 'Guller',
address: {
city: 'of mountaineers',
street: 'of the top ridges',
block: 123,
extra: {
data: {}
}
}
},
oUser = Observable.from(user);
Observable.observe(oUser, callback, {path: 'firstName'});
Observable.observe(oUser, callback, {path: 'address.city'});
Observable.observe(oUser, callback, {pathsOf: 'address'});
Observable.observe(oUser, callback, {pathsFrom: 'address'});