use RXJS to keep triggering http calls in angular until a condition is met

skyleguy

I am making calls to the spotify api which returns an object like so:

{
  next: 'https://api.spotify.com/v1/me/tracks?offset=100&limit=50'
  items: [...]
}

where items are the result of the just-made network call and next is the url to use to get the next 'page' of results. What i would like to do is make an initial call with no offset, and then keep making network calls until next is null, meaning the user has no more items left to get. If there are no more items they send back null for next.

This seems possible but cant seem to figure out how to do it correctly. What I am looking for is something like this:

  readonly baseSpotifyUrl: string = 'https://api.spotify.com/v1';

constructor(private http: HttpClient) {}


public getTracks(url?: string): Observable<any> {
    return this.http.get(url && url?.length > 0 ? url : `${this.baseSpotifyUrl}/me/tracks?limit=50`);
  }

public getAllTracks(): Observable<any> {
    const tracks$: Subject<any> = new Subject();
    let next: string = '';
    this.getTracks(next)
      .subscribe({
        next: (res: any): void => {
          tracks$.next(res.items);
          next = res.next;
          // if res.next !== null, do this again now that we have set next to res.next
        },
        error: (err: any): void => {
          console.error(err);
        }
      });
    return tracks$;
  }

The idea here is that my component will call getAllTracks() and receive a subject and then new items will be continuously pushed through that subject until all the items have been retrieved. I cannot seem to figure out how to make a new network request when the previous one returns ONLY IF there are more items to get (res.next !== null)

EDIT-----------------------------------------------------------

This gets the job done but I feel that its trash:

  public getAllTracksSegment(itemsSubject: Subject<any>, nextSubject: Subject<string>, url?: string): void {
    this.http.get(url && url?.length > 0 ? url : `${this.baseSpotifyUrl}/me/tracks?limit=50`).subscribe({
      next: (res: any): void => {
        itemsSubject.next(res.items);
        nextSubject.next(res.next);
      }
    });
  }

  public getAllTracks(): Observable<any> {
    const tracks$: Subject<any> = new Subject();
    const next$: Subject<string> = new Subject();
    next$.subscribe({
      next: (next: any): void => {
        if (next !== null) {
          this.getAllTracksSegment(tracks$, next$, next);
        }
      }
    });
    next$.next('');
    return tracks$;
  }
Picci

If I understand the issue right, I would use the expand operator to build a solution.

Here the code I would use. Comments are inline

public getTracks(url?: string): Observable<any> {
    return this.http.get(url && url?.length > 0 ? url : `${this.baseSpotifyUrl}/me/tracks?limit=50`);
}

public getAllTracks(): Observable<any[]> {
  // the first call is with no parameter so that the default url with no offset is used
  return getTracks().pipe(
     // expand is used to call recursively getTracks until next is null
     expand(data => data.next === null ? EMPTY : getTracks(data.next)),
     // with tap you can see the result returned by each call
     tap(data => console.log(data)),
     // if you want you can use the reduce operator to eventually emit the 
     // accumulated array with all items
     reduce((acc, val) => {
       acc = [...acc, ...val.items]
       return acc
     }, [])
  )
}

// now you can fire the execution of the recursive calls by subscribing
// to the observable returned by getAllTracks
getAllTracks().subscribe(
   // allItems is an array containing all the items returned by the various calls
   allItems => console.log(allItems)
)

ADDITIONAL EXPLANATIONS after the comments of @skyleguy

The tap operator is used to implement side effects. In other words it receives all the notifications from the upstream, does whatever it needs to do with the data notified and then passes downstream the same notification. There is no need to return anything from the function passed to the tap operator. The upstream is just passed downstream after the side effect is applied. In this example the side effect is simply the print on the console of the data passed with the notification.

The reduce used within the pipe is the reduce operator of RxJs and not the reduce method of Array. The reduce RxJs operator accumulates all the data notified from upstream and emits only one value when upstream completes. So, in this example, every time the call to the remote function returns something, this something enters the reduce operator and contributes to the accumulation logic. When expand returns the EMPTY Observable, at the end of the recursion, the EMPTY Observable just completes without notifying anything, which means that upstream completes and therefore reduce can emit its first and only notification, i.e. the array with all items accumulated, and then complete.

This stackblitz replicate this logic with a simulation of the remote call.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

rxjs angular call webservice until condition met

Async HTTP call until condition is met in Angular

How to retry a http request until a condition is met using rxjs

RXJS Repeat query until a condition is met?

Rxjs observable wait until some condition is met

Keep calling a function until a condition is met Python

Trying to make function keep running until a condition is met

How to keep checking array until certain condition is met

How to schedule a macro, and to keep trying it until a condition is met?

How to keep a value constant until a certain condition is met

SQL use LAG to keep looking for value until criteria is met

How to realize looping mechanism, where function calls self until some condition is not met, or queueing function calls

Angular 9 - Guard: canActivate wait until specific condition is met

Angular rxjs sets of sequential http calls

rxJS observable to repeat call every time condition is met in Angular 2

How to execute an rxjs transformation operator only if a condition is met with angular

Awaitatility, polling until condition is met

Continuos loop until condition is not met

Do action until condition is met

Loop in JavaScript until a condition is met

Collapse Cells until a condition is met

R sample until a condition is met

Javascript looping until condition met

Repeat prompt until condition is met

Execute the loop until the condition met

R loop until condition is met

Finding row until condition is met

how to keep data from lines in file until condition met later in file python

How do I keep passing the output of a Halide pipeline back into the pipeline until some condition is met?