Observing Collections in typescript

Viewed 1108

I am using RxJS in typescript and would like to have a generic way to handle changes in collections. A very naive implementation would be something like

Observable<Element[]>

where every single change in the collection turns into a "broadcast" of all the items included.. This is very cumbersome (and possibly not efficient) to deal with, but my main worry is that it requires checking on the Observer side what has changed.

Let´s imagine that Element is defined like:

class Element {
  id: string,
  propA: string,
  propB: string,
  hash(): number {..} // generates a hash based on the properties
}

in this case I would like to be notified if and when any of the Elements is added, removed or modified (an Element is considered modified if the id remains the same, but some of the properties do not).

In that case I would need some kind of extra Observable pipeline operations that receive the collection and based on the stored (previous) values generates a CollectionChangedEvent, which has a type (i.e. Added, Removed, Modified) and an id (of the target Element). The modifications being calculated based on the hash of each element.

class CollectionChangedEvent {
  type: ChangeType,
  id: string
}

Is there something similar already implemented or a better way to go about this?

2 Answers

Have you taken a look at .scan()? I think it's appropiate for this. Assuming the source is source:Observable<Element[]>, I would do something like:

const result:Observable<CollectionChangedEvent[]> = source
    .scan((previous, newElements:Element[]) => {
        const previousElements:Element[] = previous.elements;
        const newChanges = [];
        /* [...] Do huge comparison here, pushing each change you find between newElements and previousElements into newChanges */
        return {
            elements: newElements,
            changes: newChanges
        }
    }, { // Initial values: They are empty arrays
        elements: [],
        changes: []
    })
    .map(acc => acc.changes)

Something like that. If you need the result observable to be of single CollectionChangedEvent instead of an array, you can then mergeMap it to arr => Observable.from(arr)

Or you can consider doing the oposite, if you have a source stream that gives you CollectionChangedEvent, then you can build your result obserable of Element or Element[] by using scan(). This should be easier, because you can treat each event as an imperative command, rather than having to compare arrays for changes.

Maybe this is enough for you:

class Observable extends EventEmitter {
  constructor(protected callback: Function) {
    super();
    this.on("change", callback);
  }
}

class Element extends Observable {

 constructor(public id: string, callback: Function) {
    super(callback);
  }

  private _propA: string;

  set propA(v: string) {
    this.emit("change", {changed: "_propA", newV: v, oldV: this._propA});
    this._propA = v;
  }
}

This is a partial example of observable properties using the Node's EventEmitter.

Related