多个Observable订阅者

时间:2018-05-06 16:11:50

标签: angular typescript rxjs

我正在尝试使用Observables替换事件模式。一般的想法是能够监控localStorage的变化。如下所示,为此,我创建了一个Angular Injectable作为包装器:StorageService。它按预期工作:可以编写和读取项目。如果项目得到更新,读者也会获得更新。但是,一旦多个读者订阅相同的key,只有一个会收到通知。为什么会这样?我希望每个实例使用service.read('key')从一个键读取一个项目,最终得到相同的observable。最后显示的测试结果似乎并非如此。

StorageService:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/concat';
import { Observer } from 'rxjs/Observer';

interface StorageItemsDictionary { [id: string]: any; }
interface ObserverObservableDictionary { [id: string]: { observer: Observer<any>, observable: Observable<any> }; };

@Injectable()
export class StorageService {

  cache: StorageItemsDictionary;
  ood: ObserverObservableDictionary;

  constructor() {
    this.cache = {};
    this.ood = {};
  }

  /**
   * Get item from current `key` and all further changes
   * @param key
   */
  public read<T>(key: string): Observable<T | null> {
    // assure there is an ObserverObservable pair for the key:
    this.assureOOPair(key);

    // create observable from the cache or localStorage if there is an item
    if (this.cache[key] === undefined) {
      const value = localStorage.getItem(key);
      const parsed = <T>JSON.parse(localStorage.getItem(key));
      this.cache[key] = parsed;
    }

    // one time observable for current item:
    const singleTimeObservable = new Observable(singleTimeObserver => {
      singleTimeObserver.next(this.cache[key] || null);
      singleTimeObserver.complete();
    });

    // merge it with the ood
    return <Observable<T | null>>Observable.concat(singleTimeObservable, this.ood[key].observable);
  }

  /**
   * Delete item from `key`
   * @param key
   */
  public delete(key: string): void {
    delete this.cache[key];
    this.ood[key].observer.next(null);
    localStorage.removeItem(key);
  }

  /**
   * Store `item` at `key` and notify all readers
   * @param key
   * @param item
   */
  public write<T>(key: string, item: T): void {
    this.assureOOPair(key);
    this.cache[key] = item;
    this.ood[key].observer.next(item); // notify others
    const stringified = JSON.stringify(item);
    localStorage.setItem(key, stringified);
  }

  /**
   * Create a ObserverObservable pair at `key` if it does not exist
   * @param key
   */
  private assureOOPair(key: string) {
    if (this.ood[key] === undefined) {
      this.ood[key] = {
        observable: null,
        observer: null
      };
      this.ood[key].observable = new Observable(observer => {
        this.ood[key].observer = observer;
      });
      this.ood[key].observable.subscribe(e => e); // to be able to apply next
    }
  }

}

测试

  it('multiple reads', async(inject([StorageService], (service: StorageService) => {
    localStorage.removeItem('key');
    let counter = 0;
    let counter2 = 0;
    service.write('key', 'value1');

    service.read('key').forEach(v => {
      console.log('multiple reads: 1', v);
      switch (counter) {
        case 0:
          expect(v).toEqual('value1');
          break;
        case 1:
          expect(v).toEqual('value2fail'); // this case gets never called. As a result, the test passes, even though it should fail here…
          break;
      }
      counter++;
    });

    service.read('key').forEach(v => {
      console.log('multiple reads: 2', v);
      switch (counter2) {
        case 0:
          expect(v).toEqual('value1');
          break;
        case 1:
          expect(v).toEqual('value2');
          break;
      }
      counter2++;
    });

    service.write('key', 'value2');
  })));

测试日志:

multiple reads: 1 value1
multiple reads: 2 value1
multiple reads: 2 value2

1 个答案:

答案 0 :(得分:2)

我认为您正在寻找主题。主题类似于可观察对象,但您与所有订阅者共享相同的数据。每个订阅都会执行observable。我希望有所帮助。