2

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'}]

Xerri
  • 4,916
  • 6
  • 45
  • 54
  • 1
    I think this does not have to do with Observables but rather with Javascript Arrays. – Picci Mar 26 '20 at 11:52
  • I was thinking it does. I want to use something like distinctChange() but get only the changed not the whole observable – Xerri Mar 26 '20 at 11:54
  • You'll need to make a chain of operators to do that yourself because BehaviorSubject doesn't provide such thing out of the box. – martin Mar 26 '20 at 11:57
  • anything that does as it doesn't have to be a BehaviourSubject – Xerri Mar 26 '20 at 12:14
  • The complex part is the object comparison. Do you only need to compare the objects by `name` property? – frido Mar 26 '20 at 12:16
  • No, unfortunately I need to compare a long object. If anything changes, I want to know. – Xerri Mar 26 '20 at 12:17
  • What should happen if objects are added to (`[{name: 'Mark'},{name: 'Joe'}]` -> `[{name: 'Mark'},{name: 'Joe'}],{name: 'Alice'}`) or removed from (`[{name: 'Mark'},{name: 'Joe'}]` -> `[{name: 'Mark'}]`) the array? – frido Mar 26 '20 at 12:45
  • Ideally I'd like to know if there are any changes to the array. So modified, add and remove. – Xerri Mar 26 '20 at 12:50

2 Answers2

3

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

frido
  • 13,065
  • 5
  • 42
  • 56
  • Is it possible to get which object changed? – Xerri Mar 26 '20 at 14:02
  • You need a way to tell whether two different objects actually represent the same object, e.g. with ids. – frido Mar 26 '20 at 14:11
  • I already have an id on each object but how do I present it in what you explained above? – Xerri Mar 26 '20 at 14:12
  • Actually this shows the difference between one entry and another. What I want is the difference between one complete list and another. If the list has changed, I would want to know what was added, removed or modified in the entire list. – Xerri Mar 26 '20 at 14:23
  • 1
    It shows the difference between lists. `pv` and `cv` are lists and `difference` is supposed to compute the difference between those lists. To get an implementation for the function `difference` that suits your exact requirements I think it would be best to ask another question (search for a solution first) as this has nothing to do with Observables. – frido Mar 26 '20 at 14:33
  • This answer deserves more upvotes! Good job – Joosep Parts Feb 24 '22 at 04:51
1

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/

Fan Cheung
  • 10,745
  • 3
  • 17
  • 39