0
votes

Basically I'm trying to poll data from 2 endpoints for a collection of participants in a race. I have 2 methods in my service that make api requests using angular http-client and return Observables. The first method takes an array of Ids and gets back an array of the basic participant data (name, elapsed time, current checkpoint) for each Id that was passed and the second method takes a single Id and returns an array of results from each checkpoint for that participant

I have an Observable that is the result of the first service call. I then pipe and map this so that I can work with the array and then do a standard javascript array map to iterate the elements. The bit that I'm really stuck on is for each 'Participant' in the array that matches a condition, I need to make a further http call to the second endpoint and assign the value of that resulting observable to a property of the Participant. Ideally the original Observable would return as soon as it's available and then each participant in that resulting array would update with the checkpoint results as the api calls complete.

I'm only just starting learning rxjs, angular and ngrx and I'm still struggling a fair bit with Observable streams, however I've got an ngrx effect that looks like this

  @Effect()
  pollDataForCollection$ = this.collectionEventsActions$.pipe(
    ofType(CollectionActions.onCountdownRestarted.type),
    withLatestFrom(
      this.featuresStore$.select(collectionSelectors.selectAllFavourites),
      this.featuresStore$.select(collectionSelectors.selectExpandedFavourites)
    ),
    switchMap(([action, faves, expandedDetailIds]) => {
      const ids: number[] = faves.map(fave => fave.id);
      return this.apiService.getParticipantsById(ids).pipe(
        map((participants: Participants[]) => {
          return participants.map(participant => {
            if (expandedDetailIds.includes(participant.id)) {
              this.apiService.getParticipantDetailResults(participant.id).pipe(
                map((results: ParticipantResults[]) => {
                  return (participant.results = results);
                })
              );
            }
            return participant;
          });
        }),
        map(updates => {
          debugger;
          return CollectionActions.pollingDataSucceeded({ items: updates });
        }),
        catchError(error => of(CollectionActions.pollingDataErrored({ error })))
      );
    })
  );

This successfully gets the Array of participants and projects to each participant but, unsurprisingly, does nothing to assign results to the participant.

Update to include working solution

many thanks to @user2216584 for his solution

  @Effect()
  pollDataForCollection$ = this.collectionEventsActions$.pipe(
    ofType(
      CollectionActions.onCountdownRestarted.type
    )
    , withLatestFrom(
      this.featuresStore$.select(collectionSelectors.selectAllFavourites)
    )
    , switchMap(([action, faves]) => {
      const ids: number[] = faves.map(fave => fave.id);
      return this.apiService.getParticipantsById(ids);
    })
    , withLatestFrom(
      this.featuresStore$.select(collectionSelectors.selectExpandedFavourites)
    )
    , switchMap(([participants, expandedFaves]) => {
      const favesWithDetailedResults = participants.map(participant => {
        if(expandedFaves.includes(participant.id)){
            return this.apiService.getParticipantCheckpointResults(participant.id).pipe(
              map((checkpointResults: ParticipantCheckpoint[]) => {
                const participantDetail: Participant = {
                  ...participant,
                  checkpoints: checkpointResults
                }
                return participantDetail;
              })
            )
        }else{
            return of(participant)
        }
      });

      return forkJoin(favesWithDetailedResults)
    })
    , map((updates) => {
      return CollectionActions.pollingDataSucceeded({ items: updates })
    })
    , catchError(error =>
      of(CollectionActions.pollingDataErrored({ error }))
    )
  );

1

1 Answers

1
votes

This is what you need to do - For each participant you need to collect the observable from the details api and push it to an array. This array should be a collection of observables. Then use this array in forJoin function of rxjs. See the following code

- 

  @Effect()
  pollDataForCollection$ = this.collectionEventsActions$.pipe(
    ofType(
      CollectionActions.onCountdownRestarted.type
    )
    , withLatestFrom(
this.featuresStore$.select(collectionSelectors.selectAllFavourites)
,this.featuresStore$.select(collectionSelectors.selectExpandedFavourites)
    )
    , switchMap(([action, faves, expandedDetailIds]) => {
      const ids: number[] = faves.map(fave => fave.id);
      return this.apiService.getParticipantsById(ids);
),
switchMap(participants => {
 //for each participant map the detail api observable in an array
 const obs = participants.map(p => this.apiService.getParticipantDetailResults(participant.id));
return forkJoin(obs);
}),
map(joined => {
  // result from forkJoin will be an array of the api response in the order you added the observables. Also forkJoin only emit the value once for each of the observable it has the response; so it will wait for the response to comeback for the details for each participant 
 //now you can do whatever you want to do with this array
// may be parse it and send the success action to the store
})