rxjs combineLatest和Anguar2对Observable Arrays

时间:2017-02-17 09:14:38

标签: angular rxjs angularfire2

我在应用程序中进行了一些基本的更改检测,我正在苦苦挣扎。我已经在这几天失去了意志!我想知道这里是否有人可以指出我正确的方向。

我有groupuser个列表。一个用于members,另一个用于moderators

此方法连接到数据库以获取每个具有$key值的对象列表。现在我想使用这些键来获取每个用户的实际UserData。我尝试了所有的东西,combineLatest是我唯一可以开始工作的东西。

因此,两者都调用该函数,我在模板上使用async管道。

members$ = myService.getMemberListByType("blah", "members");
moderators$ = myService.getMemberListByType("blah", "moderators");

我的功能看起来像这样。

  private getMemberListByType(groupKey: string, memberType: string) {

    return this.af.database.list(`groups/${groupKey}/${memberType}`)
      .switchMap(userListKeyMap => {

        console.log("UserListKeyMap", memberType, userListKeyMap);

        let usersObservables: Observable<UserData>[] = [];
        userListKeyMap.forEach((object) => {
          usersObservables.push(this.af.database.object(`users/${object.$key}`)
            .map(UserData.fromJSON));
        });

        return Observable.combineLatest(usersObservables);
      });

  }

但是,Angular未检测到此列表的更改,如下所示。假设其他地方的其他方法删除member并将其添加到moderator。在这种情况下,members列表会发出一个空的用户列表[]

我的* ngIf和* ngFor不会检测到这个新的空对象的变化。结果是用户(在模板上)现在是2个列表的一部分,但是根据日志突出显示,此下的数据非常准确。

我觉得当我在一个空数组上组合时,这是我问题的根源。没有什么能发出......所以Angular如何改变?我不知道如何解决它。在这个“空”用例中是否有另一种combineLatest的替代方案?由于我使用的是async管道,我需要一些有意义的东西。

有人能够对我的问题有所了解吗?

谢谢!

更新

我确信问题在于angular2没有检测到空的可观察列表。特别是当Observable Array有一个值时,稍后该值变为空。 Angular2将看不到空数组。我目前无法解决这个问题。我的方法是错误的,还是需要注册一个空的可观察列表?

screen of problem

  1. 蓝色=组件的加载状态良好,用户是成员。
  2. 绿色=用户已从成员移至经理/主持人。
  3. 红色=这不存在,为什么它仍然显示?

4 个答案:

答案 0 :(得分:4)

你的方法对我来说似乎是正确的。使用带有空数组的combineLatest()不应该以错误结束(类似于例如。forkJoin,使用空数组是一个有效的用例)。然后,即使它因为一个空数组而完成,你也在使用switchMap,所以这不应该是我认为的问题。

我对AngularFire2没有任何经验,因此我不知道this.af.database.object是否只发出所有项一次然后完成它会在每次更改时发出一个值(如果我猜的话,这可能是你想要的)。

无论如何,使用combineLatest()forkJoin()的一个非常常见的问题是,他们的源Observable总是必须发出至少一个项目。换句话说,如果this.af.database.object中的任何一个为空,那么combineLatest()将不会发出任何内容。

您可以确保startWith()始终至少有一个值。例如,如下所示:

userListKeyMap.forEach((object) => {
    usersObservables.push(this.af.database.object(`users/${object.$key}`)
        .startWith('{}')
        .map(UserData.fromJSON));
});

如果这样做无效,请尝试仅使用Observable.timer()代替this.af.database.objectthis.af.database.list来确保Angular2订阅它并即使在发出新项目时也会保留订阅。

答案 1 :(得分:4)

有一个简单的解决方法:

  private getMemberListByType(groupKey: string, memberType: string) {

    return this.af.database.list(`groups/${groupKey}/${memberType}`)
      .switchMap(userListKeyMap => {

        console.log("UserListKeyMap", memberType, userListKeyMap);

        let usersObservables: Observable<UserData>[] = [];
        userListKeyMap.forEach((object) => {
          usersObservables.push(this.af.database.object(`users/${object.$key}`)
            .map(UserData.fromJSON));
        });

        return usersObservables.length > 0 ? Observable.combineLatest(usersObservables) : Observable.of([]);
      });
 }

答案 2 :(得分:2)

你应该尝试这样的事情:

private getMemberListByType(groupKey: string, memberType: string) {

  return this.af.database.list(`groups/${groupKey}/${memberType}`)

           // Force the source obs to complete to let .toArray() do its job.
           .take(1)

           // At this point, the obs emits a SINGLE array of ALL users.
           .do(userList => console.log(userList))

           // This "flattens" the array of users and emits each user individually.
           .mergeMap(val => val)

           // At this point, the obs emits ONE user at a time.
           .do(user => console.log(user))

           // Load the current user
           .mergeMap(user => this.af.database.object(`users/${user.$key}`))

           // At this point, the obs emits ONE loaded user at a time.
           .do(userData => console.log(userData))

           // Transform the raw user data into a User object.
           .map(userData => User.fromJSON(userData))

           // Store all user objects into a SINGLE, final array.
           .toArray();

}

请注意。以do()开头的行仅用于解释正在进行的操作。无需将它们保留在最终代码中。

答案 3 :(得分:1)

尝试在observables列表中使用forkJoin

let userListKeyMap$ = Rx.Observable.of([{key:1}, {key:2}, {key:3}]); //mock keys
let requestUserFromKey = (key) => Rx.Observable.of({user: key}).delay(1000); //mock users

let result$ = userListKeyMap$.switchMap((userKeys) => 
 Rx.Observable.forkJoin(userKeys.map(uk => requestUserFromKey(uk.key))));

result$.subscribe((users) => console.log('got some users: ', users));
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>