Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return an Observable by mapping another Observable

Tags:

angular

rxjs

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;
    });
like image 243
rhysclay Avatar asked Jun 12 '18 05:06

rhysclay


People also ask

Does map return an observable?

We've seen that operators like map and filter are functions which take in and return observables. Each operator exposes a public function like map or filter , which is what we import from 'rxjs/operators' and pass into pipe .

What is the return type of observable?

Returns Observable Returns an Observable that emits a boolean based on the input value: If no predicate method, the value will be converted to it's Boolean value.

Can you subscribe to an observable?

Executing an Observable But the observable function does not emit any values until it is executed. The subscribe() method calls the observable's function that produces and emits data. Thus, subscribing to an observable starts a flow of data between the observable and the observer.


2 Answers

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));
    }
like image 68
Pranay Rana Avatar answered Sep 24 '22 15:09

Pranay Rana


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[]
like image 20
zafeiris.m Avatar answered Sep 23 '22 15:09

zafeiris.m