angularpromiserxjsobservablefork-join

forkJoin after APIs are completed - rxjs


So,I'm in kind of fix here. I've got 2 GET APIs. I need to do something once those APIs return success.

I did the following.

let sub1$ = this.myService.callFirstApi();
let sub2$ = this.myService.callSecondApi();

forkJoin([sub1$,sub2$]).subscribe(() => executeRestCodeNow());

The above works fine but in my project, devs are subscribing to APIs and then working on the data from the APIs in individual functions, example:

myService.ts:

firstApi(){
   return this.http.get(apiurl).pipe(map(res) => res);
}

secondApi(){
   return this.http.get(apiurl).pipe(map(res) => res);
}

component.ts File:

ngOnint = () => {
   this.getFirstApi(); 
   this.getSecondApi();
}

getFirstApi(){
   let sub1$ = this.myService.firstApi().subscribe((data) => doingSomethingWithData)
}

getSecondApi(){
   let sub2$ = this.myService.firstApi().subscribe((data) => doingSomethingWithData)
}

Now, how do I combine these 2 separate functions and use forkJoin here? Because if I put them together in one function and use forkJoin like I showed at the beginning then I get an error as

"You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, Or Iterable"

I tried returning sub1$ and sub2$ too.


Solution

  • First, to point out ur mistake, assuming the function in your component.ts file looks like this:

    getFirstApi(){
       let sub1$ = this.myService.firstApi().subscribe((data) => doingSomethingWithData)
       return sub1$ 
    }
    

    The sub$ you had returned is an Subscription not an Observable. Therefore, when provided in forkJoin you get that error.


    Now, if u want ur Observable to independently execute some code when it receive data, then execute another when both is done, there is 2 ways I could think of to achieve it.

    Option 1
    the most common and straight forward option is using the rxjs (tap) operator.

    // do not call subscribe in this function
    getFirstApi(){
       let obs1$ = this.myService.firstApi().pipe(tap((data) => doingSomethingWithData))
       return obs1$ 
    }
    
    forkJoin([this.getFirstApi(), this.getSecondApi()])
    .subscribe(() => executeRestCodeNow())
    

    or

    let sub1$ = this.myService.callFirstApi().pipe(tap((data) => doingSomethingWithData));
    let sub2$ = this.myService.callSecondApi().pipe(tap((data) => doingSomethingWithData));
    
    
    forkJoin([sub1$,sub2$]).subscribe(() => executeRestCodeNow());
    

    Option 2
    The second option is slightly complex, and require some understanding of concept of hot and cold observable. I won't explain what is hot/cold observable here, so it is up to u to do your own research.
    But to keep the answer short, this option requires to use rxjs (shareReplay) operator.

    let sub1$ = this.myService.callFirstApi().pipe(shareReplay(1));
    let sub2$ = this.myService.callSecondApi().pipe(shareReplay(1));
    
    sub1$.subscribe((data) => doingSomethingWithData);
    sub2$.subscribe((data) => doingSomethingWithData);
    
    // this will not fire duplicate API request
    forkJoin([sub1$,sub2$]).subscribe(() => executeRestCodeNow());
    

    Edit: I may have mis-used the term hot/cold observables, not really sure, maybe the more appropriate term to look for in option 2 is Multicasting