skip to Main Content

I have a piece of code that looks like this

getInformations().subscribe(
    informations => {
        let subs = [];
        for (const information of informations) {
            subs.push(getOtherDetails(information.id));
        }
        forkJoin(subs).subscribe(response => {
           //How can I Associate Information Id With The Response
            howToAssociateIdWithResponse();
     }}
);

Situation – I want to tie the responses of my second call with the ids of my first call, but I am running into issues.

Attempted – I tried the following but that seems to be throwing error

let subs: {[x:number]: Observable<any>}[] = [];
subs.push({information.id: getOtherDetails(info.id)}) 

but when I subscribed I got an error stating You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.


Update – 1 After following @BizzyBob suggestion, the code looks like the following but my other logic is running before the subscription completes it’s job. Here is what I mean

async ngOnChanges(){
    await getData(); //How to make sure below method only executes when this is really done.
    await useInformationReceivedFromgetData(); 
}
async getData(){
  getInformations().subscribe(
    informations => {
      let subs = [];
      for (const information of informations) {
         subs.push(getOtherDetails(information.id).pipe(
            map(data => ({ id: information.id, data })) // <---
         ));
      }
      forkJoin(subs).subscribe(objects => {           
         objects.forEach(({id, data}) => { /* saved to an arrray */ });
      });
   }

);
}

2

Answers


  1. You can make each "getOtherDetails observable" emit an object with the id and response data:

    getInformations().subscribe(
       informations => {
          let subs = [];
          for (const information of informations) {
             subs.push(getOtherDetails(information.id).pipe(
                map(data => ({ id: information.id, data })) // <---
             ));
          }
          forkJoin(subs).subscribe(objects => {           
             objects.forEach(({id, data}) => { /* use id and data here */ });
          });
       }
    );
    

    Note, you can simplify your code by using .map() instead of creating a subs array and pusing to it:

    getInformations().subscribe(
       informations => {
          const subs = informations.map(
             ({id}) => getOtherDetails(id).pipe(map(data => ({ id, data })))
          );
          forkJoin(subs).subscribe(responses => {           
             responses.forEach(({id, data}) => { /* use id and data here */ });
          });
       }
    );
    

    Also, putting subscribes inside of subscribes is bad news. You’d be better off to use a Higher Order Mapping Operator that will handle an "inner subscription" for you. In this case, we can use switchMap to handle subscribing / unsubscribing to your forkJoin observable:

    getInformations().pipe(
       map(informations => informations.map(
          ({id}) => getOtherDetails(id).pipe(map(response => ({ id, data })))
       ),
       switchMap(requests => forkJoin(requests))
    ).subscribe(
       responses => responses.forEach(({id, data}) => { ... })
    );
    
    Login or Signup to reply.
  2. You can lean way harder on rxjs and its pipe operators.
    Consider following code:

    const combinedResults = getInformations().pipe(
      mergeMap((informations) => {
        return forkJoin(
          informations.map((information) =>
            getOtherDetails(information.id).pipe(
              map((detail) => ({ detail, id: information.id })),
            ),
          ),
        )
      }),
    )
    
    combinedResults.subscribe((combinedDetails) => {
      for (const information of combinedDetails) {
        const { detail, id } = information
    
        console.log('Now you have access to the detail and the id')
      }
    })
    

    It might look a bit crazy on first glance, but it reflects what you want to do in an "rxjs way".

    1. fetch the general "informations"
    2. use mergeMap to use the result and map to a new observable
    3. use Forkjoin to combine the array of detail observables
    4. Map each detail response with the original response using the map pipe
    5. Subscribe only once to read the combined values

    This way enables you to easily display the result in an angular template using the async pipe. So if your goal is to display the combined values, you don’t have to subscribe in your component logic at all, which is a gain because you don’t have to think about performance or memory leaks. Read this for more info

    <div *ngFor="let item of (combinedResults | async)">{{item | json}}</div>
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search