自动初始化 BehaviorSubject 缓存全局数据

Auto-initialize BehaviorSubject to cache global data

简化用例:

  1. 我有 Angular 具有多个模块的应用程序,大多数(不是全部)模块使用机场列表
  2. 我想创建一个 global-cache.service.ts 并将机场列表缓存在 BehaviorSubject 中,它将作为 Observable 公开。我只想在用户登陆订阅该 Observable 的组件时初始化 BehaviorSubject(点击数据库)(与在服务控制器中初始化它相比)

这是起点,无法正常工作(请参阅 this.getAirpotsFromDB() 调用后的评论):

全局-cache.service.ts

airports: BehaviorSubject<string[]> = new BehaviorSubject(null);
get airports$(): Observable<string[]> {
  if (this.airports.getValue() == null) {
    //get list from db, and initialize the subject
    this.getAirportsFromDB();
    //**PROBLEM:** how do I return `this.airports.asObservable()` after it's initialized with data from the call above or return it from inside the call?
  }
  else{
    //list initialized, emit from subject (don't hit db)
    return this.airports.asObservable();
  }
}

getAirportsFromDB() {
  this.http.get<string[]>('/api/airports').subscribe((_result) => {
    this.airports.next(_result);
  });
}

myComponent1.ts

//imports global-cache.service but never subscribes to airports$ (service has many other arrays that are used here)
//User lands here first, `getAirportsFromDB()` never gets called, user goes to myComponent2

myComponent2.ts

...
airports$: any = this.globalCacheService.airports$;
//subscribed in html `airports$ | async`
...
//user lands here, `getAirportsFromDB()` is called, `airports` BehaviorSubject is initialized
//user then goes to myComponent3

myComponent3.ts

...
airports$: any = this.globalCacheService.airports$;
//subscribed in html `airports$ | async`
...
//`airports` BehaviorSubject emits last value (initialized in Component2). `getAirportsFromDB()` does not get called again

get airports() 中,您应该始终 return 可观察的。如果数据库 API 尚未被调用,则调用 getAirportsFromDB,就像您当前正在做的那样

由订阅 returned 可观察对象的组件检查 returned 数据并基于此更新它们的视图(这将是异步的)

Angular 大学网站有一些 detailed examples 的服务也可以帮助

您可以尝试类似下面的操作,只需使用 tap 运算符并将 http 请求的值保存在 BehaviorSubject 和 return ObservablegetAirportsFromDB

export class Service {

  airports: BehaviorSubject<string[]> = new BehaviorSubject(null);

  get airports$(): Observable<string[]> {
    return this.airports.getValue()
        ? this.airports.asObservable()
        : this.getAirportsFromDB();
  }

  getAirportsFromDB(): Observable<string[]> {
    return this.http.get<string[]>('/api/airports')
      .pipe(
        tap((_result) => this.airports.next(_result))
      )
  }
}

次要更新: BehaviorSubject observable 没有在第一次调用时得到 returned,所以后续调用 getter 不会更新视图,所以你可以使用上面的代码,以防万一,你将它用作缓存并且不想接收任何更新

最后选择了 shareReplay(1);方法更简单,并且完全按照我的意愿行事

global-cache.service.ts

airports$: Observable<string[]> = this.getAirportsFromDB();

getAirportsFromDB() {
  return this.http.get<string[]>('/api/airports').pipe(shareReplay(1));
}