2
votes

I am still trying to wrap my head around rxjs and observables and BehaviorSubject. What I would like to do is, combine BehaviorSubject and LocalStorage so that all components get notified when a particular LocalStorage variable changes.

For example, consider the following scenario.

  • There are two components Component1 and Component2.

  • Both these components look for a variable in LocalStorage called Component1 and Component2, which contain a color and they display a square of that color.

Component1 needs to subscribe to "Component1Color" key in LocalStorage and Component2 needs to subscribe to "Component2Color" in LocalStorage.

One way to do this is have a BehaviorSubject that maintains the state of LocalStorage and anytime a change is made to any variable, broadcast this message to all the components that have subscribed to the BehaviorSubject.

The problem with this approach is that when Component2Color is updated, component1 gets notified as well, and will do nothing about it.

What would be nice is, Component1 gets notified only when Component1Color is updated, and Component2 gets notified only when Component2Color is updated. Is there a way this can be done using a single BehaviorSubject?

1
Did you use distinctUntilChanged()? Can you post your code, so we can see what you're doing?Sasxa
I was just playing around with rxjs and don't have a working code yet. I was trying to figure out how I should create a wrapper around LocalStorage to work with the above scenario. I just looked at distinctUntilChanged and it looks like that is exactly what I wanted :) I could just pass in a comparer function for each component and that should end up firing the subscriber only if the diff function succeeded.Izaaz Yunus

1 Answers

2
votes

You can listen to the storage event via the StorageEvent

const storage = Rx.Observable.fromEvent(window, 'storage').groupBy(ev => ev.key);

Then you can access each stream by doing:

let component1Stream = storage.filter(x => x.key === 'Component1Color').merge();

//Or using flatMap
let component2Stream = storage.flatMap(x =>
  x.key === 'Component2Color' ? x : Rx.Observable.empty()
);

Edit

As was pointed out in the comments, the storage event is only useful for cross page updates it won't update if you are on the same page. Fortunately, the solution remains pretty much the same, you just need to provide a decorator object for the localStorage object so that it can emit events.

class RxLocalStorage {
  private _subject: Rx.Subject<any>;
  private _updates: Observable<any>;
  constructor() {
    this._subject = new Rx.Subject();
    this._updates = this._subject.groupBy(ev => ev.key)
      .share();
  }
  getItem(key) { return this._updates.filter(x => x.key === key).merge(); }
  setItem(key, newValue) {
    const oldValue = localStorage.getItem(key);
    const event = {key, newValue, oldValue};
    localStorage.setItem(newValue);
    this._subject.next(event);
  }
}


let myLocalStorage = new RxLocalStorage();
myLocalStorage.getItem('Component1Color')
  .subscribe(x => console.log('Component1 Color changed'));

myLocalStorage.setItem('Component1Color', 'blue');
//-> Component1 Color changed

Note the above assumes that all of your subscriptions are made before you begin making changes.