9

I would like to watch over an object, so all the subscribers will be informed for any changes of it.

I saw it already been asked before, yet the answer is irrelevant since RXjs verion 5 do not include the ofObjectChanges in it's API anymore.

I've looked at some "hacks" like creating an observer which return a function:

let myObservable =  new Observable((observer) => {
  return (data) => {
    observer.next(data)
  }
}) 
//...
myObservable.subscribe()('someData')

However, I'm sure there is more elegant way of doing it. Any Ideas?

yuval.bl
  • 4,890
  • 3
  • 17
  • 31

3 Answers3

7

The ES6 way of observing an object is with Proxies. You create a Proxy that wraps the original object and do your work on it. You can use it to create something similar to Observable.ofObjectChanges. Here a partial implementation (only set. You'd need to implement the other traps):

Observable.ofProxyChanges = (target) => {
  let subject = new Subject
  let proxy = new Proxy(target, {
    set(target, key, val) {
      let oldValue = target[key]
      target[key] = val
      subject.next({
        type: oldValue === undefined ? "add" : "change",
        object: target,
        name: key,
        oldValue: oldValue
      })
    }
  })
  return [proxy, subject.asObservable()]
}

let [obj, objChange$] = Observable.ofProxyChanges({})
objChange$.subscribe(console.log)

obj.bar = 1 // logs { type: "add", name: "bar", object: { bar: 1 } }
obj.foo = 2 // logs { type: "add", name: "foo", object: { bar: 1, foo: 2 } }
obj.foo = 3 // logs { type: "change", name: "foo", object: { bar: 1, foo: 3 }, oldValue: 2 }
3

I would suggest using something similar to redux approach, when changes to the object can be made in predefined way:

function factory(reducerByType, initialState) {
  const action$ = new Rx.Subject();
  const state$ = action$
    .startWith(initialState)
    .scan((state, action) => {
      if (reducerByType.hasOwnProperty(action.type)) {
        return reducerByType[action.type](state, action);
      }
      
      return state;
    })
    .distinctUntilChanged();
  
  
  return {
    action$,
    state$,
    dispatch: action => action$.next(action)
  }
}

const {state$, dispatch} = factory({
  ADD: (state, action) =>  state + action.number,
  SUBTRACT: (state, action) =>  state - action.number,
}, 0);

state$.subscribe(val => console.log(val));

dispatch({
  type: 'ADD',
  number: 10,
});

dispatch({
  type: 'SUBTRACT',
  number: 15,
});

dispatch({
  type: 'SUBTRACT',
  number: 0,
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.0/Rx.js"></script>
Lukas Neicelis
  • 438
  • 3
  • 6
0

You need to use Behavior Subject . https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/behaviorsubject.md

Vamshi
  • 9,194
  • 4
  • 38
  • 54