如何从键列表创建 ObservableList?

How to create an ObservableList from a list of keys?

我正在尝试显示属于某个用户的所有 "exercises"。因为一个 "exercise" 对象可以属于多个用户,这是典型的多对多关系。由于文档建议扁平化数据库结构,因此每个用户都有一个 { $exercise_key: true }.

的字典

经过几个小时的搜索,我终于得到了一些有用的东西,但是它很丑,而且看起来很慢而且效率低下。我首先从用户那里得到一个可观察的练习键列表,然后对于这些键中的每一个,我得到一个 FirebaseObservableObject 作为 'metadata' 属性附加。

当我加载页面时,首先我看到一个空的项目符号列表,然后只有一半的项目符号有它们的文本,最后是其余的。整个事情需要大约 2 秒才能显示出来。 "flicker" 即使在我动态添加新练习时也会发生,而无需重新加载页面。请在 https://papa-bear-1f486.firebaseapp.com 上自行尝试(使用 google auth)。

我的数据库是这样的:

{
  "exercises" : {
    "-KeVLI3UbGkRbifLffMa" : {
      "createdAt" : 1488748884471,
      "name" : "squat1"
    },
    "-KeVLISTWaXbFxS6yqfv" : {
      "createdAt" : 1488748886066,
      "name" : "squat2"
    },
  },
  "users" : {
    "JtLTkOb8EXTeCeasF6vLpIpoUDB2" : {
      "exercises" : {
        "-KeVLI3UbGkRbifLffMa" : true,
        "-KeVLISTWaXbFxS6yqfv" : true,
      }
    }
  }
}

这是我想出的丑陋代码:

private exercisesKey$: FirebaseListObservable<any[]>;
private exercises$: Observable<any[]>;

constructor(private af: AngularFire, private auth: AuthService) {
  const path = '/users/' + auth.id + '/exercises';

  this.exercisesKey$ = af.database.list(path);
  this.exercises$ = this.exercisesKey$.map((items) => {
    items.map(item => {
      item.metadata = this.getExercise(item.$key);
      return item;
    });
  return items;
});

getExercise(key: string): FirebaseObjectObservable<IExercise> {
    return this.af.database.object('/exercises/' + key);
}

在我看来:

<ul>
  <li *ngFor="let exercise of exerciseService.exercises$ | async">
    {{ (exercise.metadata | async)?.name }}
  </li>
</ul>

所以我想我的问题是:如何从键列表中获取可观察的对象列表?

package.json:

"angularfire2": "^2.0.0-beta.5",
"firebase": "^3.5.2",

您无法避免对每个练习进行查询(除非您想执行一个查询来检索每个练习)。但是,您可以稍微简化一下。

您可以使用 combineLatest 来编写一个 observable of exercises:

import 'rxjs/add/observable/combineLatest';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/switchMap';

constructor(private af: AngularFire, private auth: AuthService) {
  const path = '/users/' + auth.id + '/exercises';
  this.exercisesKey$ = af.database.list(path);
  this.exercises$ = this.exercisesKey$
    .switchMap((items) => items.length === 0 ?
      Observable.of([]) :
      Observable.combineLatest(...items.map(item => this.getExercise(item.$key)));
    );
});

如果练习键列表发生变化或者练习本身发生变化,组合的可观察对象将重新发出练习列表。

如果您只想要练习的快照而不想要在练习发生变化时重新发出的可观察对象,请使用 forkJoin 而不是 combineLatest 并使用 first完成每个可观察的练习:

import 'rxjs/add/observable/forkJoin';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/first';
import 'rxjs/add/operator/switchMap';

constructor(private af: AngularFire, private auth: AuthService) {
  const path = '/users/' + auth.id + '/exercises';
  this.exercisesKey$ = af.database.list(path);
  this.exercises$ = this.exercisesKey$
    .switchMap((items) => items.length === 0 ?
      Observable.of([]) :
      Observable.forkJoin(...items.map(item => this.getExercise(item.$key).first()));
    );
});

在这两种情况下,您的模板将如下所示:

<ul>
  <li *ngFor="let exercise of exerciseService.exercises$ | async">
    {{ exercise.name }}
  </li>
</ul>