Home > Enterprise >  Handling nested subscribes with a dependency
Handling nested subscribes with a dependency

Time:12-29

I have a model that looks like:

export class Info {
  Id: number;
  Participant: object;
}

I have an endpoint which fetches a list of Info objects, but it does not fetch the Participant properties. I need to fetch these objects from a different service, but I'm having trouble understanding how to work with multiple Observables and one that depends on the previous value returned.

I'm fetching an array of Info and then going through the array to fetch the Participant object from the other service. I'm assigning the view portion of my component outside of the observable so I'm encountering issues displaying the value until the value is actually there.

Partial.html

<tr *ngFor="let connection of connections">
    <td> {{connection.Id}} </td>
    <td> {{connection.Participant.Name}} </td>
</tr>

Component.ts

ngOnInit() {
    this.connectionInfoService.GetInfo().subscribe((connections: Info[]) => {
      connections.forEach(x => {
        this.participantService.getParticipantByID(x.Id).subscribe((participant) => {
          x.Participant = participant;
        });
      });

      this.connections = connections;
   });
}

I get Cannot read properties of null (reading 'Name') in the console because Name is not a property of null obviously until there is actually a value for Participant. How can I handle this better? I would rather handle it within my ngOnInit service fetching code, but I don't know how to handle an Observable that depends on another Observable like that.

CodePudding user response:

You should never have a subscribe inside of another subscribe. Use RxJs operators when you need to transform the data that's being emitted from an Observable.

The following code is untested, but hopefully it's close enough to get you on the right track:

import { combineLatest } from 'rxjs';
import { map, switchMap} from 'rxjs/operators';

// ...


  ngOnInit() {
    this.connectionInfoService.GetInfo().pipe(
      switchMap(connections => {
        // For each connection...
        const connections$ = connections.map(connection => {
          // Create an Observable to fetch the participant.
          const participant$ = this.participantService.getParticipantByID(connection.Id);
          return participant$.pipe(
            // When the participant is recieved, add the participant
            // to the connection and return the connection.
            map(participant => {
              connection.Participant = participant;
              return connection;
            })
          );
        });
        // Use the RxJs combineLatest function to return an Observable that
        // emits when all the connections$ Obervables have emitted.
        return combineLatest(connections$);
      })
    ).subscribe(connections => this.connections = connections);
  }

Aside: I used the the convention of adding a trailing $ to variables that reference an Observables. You can read more about that convention here.

The key RxJs operator in this sample is the switchMap operator. With this operator, every time the outer Observable emits, switchMap will effectively subscribe to the inner Observable. In this case, the outer Observable is this.connectionInfoService.GetInfo() and the inner Observable is combineLatest(connections$).

Bonus

Instead of subscribing to the Observable in the component class, consider using Angular's async pipe:

  ngOnInit() {
    this.connections$ = this.connectionInfoService.GetInfo().pipe(
      // same as shown above.
    );
  }
<tr *ngFor="let connection of connections$ | async">
    <td> {{connection.Id}} </td>
    <td> {{connection.Participant.Name}} </td>
</tr>
  • Related