11
votes

I am trying to map an observable, get a value from my returned observable then feed this value into another observable and return that result. Here is what I have so far:

  getJobsByUser(user: User): Observable<Job[]> {
    return this.getUsersGroupsAsObservable(user.uid, 'contacts').map(groups => {
      groups.map(group => {
        this.getJobsbyGroup(group.id);
      });
    });

  getJobsbyGroup(groupId: string): Observable<Job[]> {
    return this.afs
      .collection<Job>('jobs', ref => ref.where(`group.${groupId}`, '==', true))
      .valueChanges();
  }

  getUsersGroupsAsObservable(
    userId: string,
    type: string = 'users',
  ): Observable<Group[]> {
    return this.afs
      .collection<Group>('groups', ref =>
        ref.where(`${type}.${userId}`, '==', true),
      )
      .valueChanges();
  }

The problem is typescript is indicating that my getJobsByUser function will return an observable of type:void. When I do output it on my template I get nothing or undefined. I feel like I need to use switchMap but im a little fuzzy with rx/js. I am unsure how to return an Observable of type Job[]

Update: With help from @Pranay Rana I am now returning array, and can get the first value like this:

  getJobsByUser(user: User): Observable<Job[]> {
    return this.getUsersGroupsAsObservable(user.uid, 'contacts').pipe(
      mergeMap(groups => {
        // returns an array of groups - we need to map this
        return this.getJobsbyGroup(groups[0].id); // works with the first value - do we need another map here?
      }),
    );
  }

Update 2: I have managed to get some data back from firestore, but it is emitting multiple observables rather than a combined stream:

this.fb.getUsersGroupsAsObservable(user.uid, 'contacts')
   .switchMap(groups => {
      return groups.map(group => this.fb.getJobsbyGroup(group.id));
   })
    .subscribe(res => {
       console.log(res);
       // this emits multiple observables rather than one
       this.job$ = res;
    });
2
try return this.getJobsbyGroup(group.id);Ramesh Rajendran
probably want to use flatMapSachila Ranawaka
Nearly there thank you - i noticed that the mergeMap is returning an array - I will post an updaterhysclay
@rhysclay - no problem , fix itPranay Rana
Sorry but now i am getting 'concatMap' does not exist on type Observable<Group[]>rhysclay

2 Answers

13
votes

Below approach discussed in detail at : Way to handle Parallel Multiple Requests

Below approach make use of mergemap

getJobsByUser(user: User) {
     return this.getUsersGroupsAsObservable(user.uid, 'contacts').pipe(
       mergeMap(group => this.getJobsbyGroup( group.id))
     );
}

callingfunction(){
  const requests = this.getJobsByUser(this.user);
  requests.subscribe(
  data => console.log(data), //process item or push it to array 
  err => console.log(err));
}

you can also make use of forkJoin

getJobsByUser(user: User) {
         return this.getUsersGroupsAsObservable(user.uid, 'contacts').pipe(
           map(group => this.getJobsbyGroup( group.id))
         );
    }

    callingfunction(){
      const requests = forkJoin(this.getJobsByUser(this.user));
      requests.subscribe(
      data => console.log(data), //process item or push it to array 
      err => console.log(err));
    }
7
votes

Ok, so first you are missing two return statements in getJobsByUser function (shown below with capital RETURN:

getJobsByUser(user: User): Observable<Job[]> {
    return this.getUsersGroupsAsObservable(user.uid, 'contacts').map(groups => {
      RETURN groups.map(group => {
        RETURN this.getJobsbyGroup(group.id);
      });
    });

Or, a bit more elegantly:

getJobsByUser(user: User): Observable<Job[]> {
    return this.getUsersGroupsAsObservable(user.uid, 'contacts')
      .map(groups => groups.map(group => this.getJobsbyGroup(group.id)));
}

Now we need to flatten the result, because, if I get this right, 1 user has many groups and each group many jobs and you want your final result to be just a Job[] with all the jobs of all the groups of the user.

You can use several operators to flatten results, e.g. switchMap, or concatMap. Here is an example:

getJobsByUser(user: User): Observable<Job[]> {
    return this.getUsersGroupsAsObservable(user.uid, 'contacts')
      .concatMap(groups => groups)
      .concatMap(group => this.getJobsbyGroup(group.id))
      .concatMap(jobs => jobs)
      .toArray();
}

What each line says is this:

  1. Get me the groups for a user // returns Observable<Group[]>
  2. Flatten the result of Group[] // returns Observable<Group>
  3. Get me the jobs for a group // returns Observable<Job[]>
  4. Flatten the result of job[] // returns Observable<Job>
  5. Gather all events of Job to one event that has a Job[]