How can I get which object changed in a BehaviourSubject in RxJs?

Viewed 535

I am listening to an observable an after the first emit with all the objects, I would to get only the object that changed. So if I have:

[{name: 'Mark'},{name: 'Joe'}]

and then a name change I only get the object that changed. So if the object becomes:

[{name: 'Jean Mark'},{name: 'Joe'}]

I only get

[{name: 'Jean Mark'}]

2 Answers

Your Observable emits arrays and you want to know the difference between the currently emitted array and the previous one. Tracking array state changes has more to do with how to compare arrays or objects than with Observables.

If you want to track changes within an Observable it really comes down to comparing a previous with a current value. The logic you want to use here is up to you. e.g. you have to think about how to distinguish between a 'modified' value and newly 'added' value in an array?

Check out these questions to get you started:

You can compare the current value cv to the previous one pv in an Observable by using pairwise. Here is a how it could look like.

const source = of(
  [{ name: "Mark", p: 2 }, { name: "Joe", p: 3 }],
  [{ name: "Jean Mark", p: 2 }, { name: "Joe", p: 3 }],
  [{ name: "Jean Mark", p: 1 }, { name: "Joe", p: 3 }, { name: 'Alice' }],
  [{ name: "Jean Mark", p: 1 }, { name: "Joe", p: 3 }],
  [{ name: "Jean Mark", p: 1 }, { name: "Joe", p: 4 }],
  [{ name: "Jean Mark", p: 1 }, { name: "Joe", p: 4 }]
);

// compare two objects
const objectsEqual = (o1, o2) =>
  typeof o1 === "object" && Object.keys(o1).length > 0
    ? Object.keys(o1).length === Object.keys(o2).length &&
      Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
    : o1 === o2;

// compare two arrays 
// REPLACE this function with YOUR OWN LOGIC to get your desired output !!!
const difference = (prev, curr) => ({ 
  added: curr.filter(o1 => !prev.some(o2 => objectsEqual(o1, o2))),
  removed: prev.filter(o1 => !curr.some(o2 => objectsEqual(o1, o2)))
})

source.pipe(
  startWith([]), // used so that pairwise emits the first value immediately
  pairwise(), // emit previous and current value
  map(([pv, cv]) => difference(pv, cv)) // map to difference between pv and cv
).subscribe(console.log);

https://stackblitz.com/edit/rxjs-m9ngjy?file=index.ts

You can watch an array (index value/add/remove) with javascript proxy, but that doesn't watch for object change in the array.

const handler = {
set: function(target, property, value, receiver){
   console.log('setting ' + property + ' for ' + target + ' with value ' + value);
   target[property] = value;
   return true;

  }
}


const arr=[{name: 'Mark'},{name: 'Joe'}];
const proxy = new Proxy(arr, handler);
// will log
proxy[0]="hello"
// won't log
proxy[0].name="ben"

if you also want to watch for object change then you need to either use proxy for every object added, or create your to be added object with Object.defineProperty() and add your setter

There is also an existing library that watch for both object and array change, and it also use proxy https://github.com/ElliotNB/observable-slim/

Related