遍历 Observable 对象数组并将来自另一个 Observable 的附加数据添加到每个对象

Iterate through Observable object-array and add additional data from another Observable to every object

我有一个 Angular 服务返回一个“cleaningDuty”对象数组。在职责对象中,有一个名为“currentCleaner”的嵌套对象,其 id.

[
  {
    // other cleaningDuty data
    currentCleaner: {
      id: string;
      active: boolean;
    };
  },
  {
    // other cleaningDuty data
    currentCleaner: {
      id: string;
      active: boolean;
    };
  },
  {
    // other cleaningDuty data
    currentCleaner: {
      id: string;
      active: boolean;
    };
  }
]

currentCleaner.id 的帮助下,我想使用相同的 pipe() 方法从 UserService 动态获取 currentCleaner 的用户数据,并将返回的用户数据添加到 cleaningDuty 对象。那么对象应该是这样的:

{
  // other cleaningDuty data
  currentCleaner: {
    id: string;
    active: boolean;
  },
  cleanerData: {
    name: string;
    photoUrl: string;
    // other user data
  }
},

不幸的是,即使投入了几天时间,我还是无法让它工作。我几乎尝试了 forkJoin()mergeMap() 等的所有组合。我知道目标组件中的嵌套 subscribe() 方法可以完成工作,但我想编写质量最好的代码。这是我当前的服务方法状态(它将用户可观察值而不是值添加到 cleaningDuty 对象):

getAllForRoommates(flatId: string, userId: string) {
    return this.firestore
      .collection('flats')
      .doc(flatId)
      .collection('cleaningDuties')
      .valueChanges()
      .pipe(
        mergeMap((duties) => {
          let currentCleaners = duties.map((duty) =>
            this.userService.getPublicUserById(duty.currentCleaner.id),
          );
          return forkJoin([currentCleaners]).pipe(
            map((users) => {
              console.log(users);
              duties.forEach((duty, i) => {
                console.log(duty);
                duty.cleanerInfos = users[i];
              });
              return duties;
            }),
          );
        }),
      );
  }

getPublicUserById()方法:

getPublicUserById(id: string) {
    return this.firestore.collection('publicUsers').doc(id).valueChanges();
  }

如有任何帮助,我们将不胜感激!

  1. 检查您是否需要 switchMapmergeMap。 IMO 我会在 valueChanges() 发出时取消现有的可观察对象。请参阅 了解它们的差异 b/n。
  2. 使用Array#map with RxJS forkJoin函数并行触发请求。
  3. 使用 RxJS map operator with destructuring 语法为现有对象添加额外的属性。
  4. 应该使用理想定义的类型而不是 any 类型。

尝试以下方法

getAllForRoommates(flatId: string, userId: string) {
  return this.firestore
    .collection('flats')
    .doc(flatId)
    .collection('cleaningDuties')
    .valueChanges()
    .pipe(
      switchMap((duties: any) =>
        forkJoin(
          duties.map((duty: any) =>          // <-- `Array#map` method
            this.userService.getPublicUserById(duty.currentCleaner.id).pipe(
              map((data: any) => ({          // <-- RxJS `map` operator
                ...duty,                     // <-- the `currentCleaner` property
                cleanerData: data            // <-- new `cleanerData` property
              }))
            )
          )
        )
      )
    );
}

forkJoin 函数将:

Wait for Observables to complete and then combine last values they emitted; complete immediately if an empty array is passed.

所以,你必须确保所有内部 Observables 都已经完成,然后 forkJoin 将发出组合值,你可以使用 take(1) 运算符来实现,如下所示:

getAllForRoommates(flatId: string, userId: string) {
  return this.firestore
    .collection('flats')
    .doc(flatId)
    .collection('cleaningDuties')
    .valueChanges()
    .pipe(
      mergeMap((duties) =>
        // The `forkJoin` will emit its value only after all inner observables have been completed.
        forkJoin(
          duties.map((duty) =>
            // For each duty, fetch the cleanerData, and append the result to the duty itself:
            this.userService.getPublicUserById(duty.currentCleaner.id).pipe(
              take(1), // to complete the sub observable after getting the value from it.
              map((cleanerData) => ({ ...duty, cleanerData }))
            )
          )
        )
      )
    );
}

首先,我建议您改进应用程序的打字,这将帮助您更清楚地看到问题。 对于我的回答,我假设您定义了 3 个接口 CurrentCleaner、CleanerData 和 RoomData,它们只是:

interface RoomData {
    currentCleaner: CurrentCleaner
    cleanerData: CleanerData
}

然后你可以编写一个辅助函数来从当前清洁工那里检索房间数据:

getRoomData$(currentCleaner: CurrentCleaner): Observable<RoomData> {
    return this.userService.getPublicUserById(currentCleaner.id).pipe(
        map(cleanerData => ({ currentCleaner, cleanerData }))
    )
}

如您所见,此函数采用当前清洁器,为其获取清洁器数据并将其映射到 RoomData 对象。

下一步是定义您的主要功能

getAllForRoommates(flatId: string, userId: string): Observable<RoomData[]> {
    return this.firestore
        .collection('flats')
        .doc(flatId)
        .collection('cleaningDuties')
        .valueChanges()
        .pipe(
            mergeMap((duties) => {
                // here we map all duties to observables of roomData
                let roomsData: Observable<RoomData>[] = duties.map(duty => this.getRoomData$(duty.currentCleaner));

                // and use the forkJoin to convert Observable<RoomData>[] to Observable<RoomData[]>
                return forkJoin(roomsData)
            }),
        );
    }

我认为这种方式很容易理解代码在做什么。