Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

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

I have a Angular service returning an array of "cleaningDuty" objects. Inside a duty object, there is a nested object called "currentCleaner" with an 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;
    };
  }
]

with the help of the currentCleaner.id I want to fetch the user data of the currentCleaner from a UserService dynamically in the same pipe() method and add the returned user data to the cleaningDuty object. Then the object should look like this:

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

Unfortunately I just cant get this to work even after investing days into it. I tried almost every combination from forkJoin(), mergeMap() and so on. I know a nested subscribe() method inside the target component would get the job done, but I want to write the best code possible quality-wise. This is my current state of the service method (it adds the user observable instead of the value to the cleaningDuty object):

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;
            }),
          );
        }),
      );
  }

The getPublicUserById() method:

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

Any help would be greatly appreciated!

about 4 years ago · Juan Pablo Isaza
3 answers
Answer question

0

The forkJoin function will:

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

So, you have to make sure that all the inner Observables have been completed, then the forkJoin will emit the combined values, and you can achieve that using take(1) operator, like the following:

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 }))
            )
          )
        )
      )
    );
}
about 4 years ago · Juan Pablo Isaza Report

0

Firstly I would suggest you to improve the typings on your application, that will help you to see the issue more clear. For my answer I will assume you have 3 interfaces defined CurrentCleaner, CleanerData and RoomData wich is just:

interface RoomData {
    currentCleaner: CurrentCleaner
    cleanerData: CleanerData
}

Then you can write a helper function to retrieve the room data from a current cleaner:

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

As you can see, this functions take a current cleaner, gets the cleaner data for it and maps it into a RoomData object.

Having this next step is to define your main function

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)
            }),
        );
    }

I think this way is easy to understand what the code is doing.

about 4 years ago · Juan Pablo Isaza Report

0

  1. Check if you need switchMap or mergeMap. IMO I'd cancel the existing observable when the valueChanges() emits. See here for the difference b/n them.
  2. Use Array#map with RxJS forkJoin function to trigger the requests in parallel.
  3. Use RxJS map operator with destructuring syntax to add additional properties to existing objects.
  4. Ideally defined types should be used instead of any type.

Try the following

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
              }))
            )
          )
        )
      )
    );
}
about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!