1

How can I traverse through sequential API calls which is in a linked list structure? Right now I'm making a request each 5 seconds where I get OrdersList data. This object contains a field results with an array of Order objects and it also contains a next field with a request URL to access the next array of orders since they are not sent all at once.

My orders.component.ts:

ngOnInit(): void {
    this.subscription = timer(0, 5000).pipe(
        switchMap(() => this.apiManager.fetchShopOrders())
      ).subscribe(orders => {
      
            const loadedOrders: OrdersList = orders;

            /* not working - try to get next set(s) of orders */
            if(loadedOrders.next != null){
                while(loadedOrders.next != null){
                    let response = this.apiManager.fetchNextSetOfOrders(loadedOrders.next).toPromise();
                    response.then((nextSetOrders) => {
                        const loadedNextSetOrders: OrdersList = nextSetOrders;
                        loadedOrders.next = loadedNextSetOrders.next;
                        loadedOrders.results = [...loadedOrders.results, ...loadedNextSetOrders.results];
                    })
                } 
            }
            ...
            /* do stuff with the orders */
            ...
    });
}

My ApiManagerService:

@Injectable({ providedIn: 'root' })
export class ApiManagerService {

    private API_KEY = "...";
    private API_SECRET = "...";
    private token = "...";
    private webzine_id: string = "...";

    constructor(private http: HttpClient) {}

    fetchShopOrders() {
        const url = "https://commerce.ww-api.com/commerceapi/v1/order/" + this.webzine_id + "/order/?per_page=10";
        return this.http
            .get<OrdersList>(url, {
                headers: new HttpHeaders({
                    "API-KEY": this.API_KEY,
                    "API-SECRET": this.API_SECRET
                }),
            });
    }

    fetchNextSetOfOrders(nextSetUrl: string) {
        return this.http
            .get<OrdersList>(nextSetUrl, {
                headers: new HttpHeaders({
                    "API-KEY": this.API_KEY,
                    "API-SECRET": this.API_SECRET
                }),
            });
    }
}

How can I make it possible to sequentially add the next set of orders to my OrdersList object?

Thanks in advance.

eko
  • 39,722
  • 10
  • 72
  • 98
Manuel Brás
  • 413
  • 7
  • 21
  • `expand` might help: https://www.learnrxjs.io/learn-rxjs/operators/transformation/expand – eko May 11 '21 at 14:01
  • @eko seems a bit chinese for an angular noob like myself ;_; – Manuel Brás May 11 '21 at 14:27
  • I can have a look later but is it only 1 level of recursion? What I mean is if you fetch the url from the `next` field, could it also contain a `next` field? – eko May 11 '21 at 15:56
  • @eko When the url in the `next` field is called, another `OrdersList` object is returned which may also contain a `next` field. Basically you can view it as returning pages of orders. If you have 50 orders and set 10 orders per list, then 5 lists will exist, each of them containing a `next` field to access the next set of orders. Of course, the 5th page would have the `next` field set to `null`. – Manuel Brás May 11 '21 at 16:06
  • ok so `orders` which is the response of your subscription is a linked list, right? – eko May 11 '21 at 16:57
  • @eko Yeah, I guess you can view it as the head of the linked list. Through it I want to sequentially access the missing orders using the URL in the `next` field, adding them set by set to the `results` field which contains the `orders` array. – Manuel Brás May 11 '21 at 17:27
  • Could you check this and tell me if it solves your issue? https://stackblitz.com/edit/angular-1zitjd?file=src%2Fapp%2Fapp.component.ts I can explain it more if it seems too complex but basically I used the `expand` operator to recursively call `fetchNextSetOfOrders` until the `next` field is undefined in the response. For my example I sent the `next` as undefined for the 6th order. But lemme know how it goes. – eko May 11 '21 at 17:34
  • @eko Unfortunately I don't seem to be able to adapt your answer to what I have right now. Not that your solution is wrong by any means, it's just lack of knowledge and experience on my part. – Manuel Brás May 11 '21 at 20:01
  • What seems to be the problem? You will recurse the `next` fields like I did and eventually it will arrive undefined and go out of that recursion. Meanwhile you will be pushing the `results` to an array. – eko May 11 '21 at 20:04
  • @eko I did add the `pipe()` from your example. However, when I `console.log(this.results)` inside `subscribe()` it keeps printing and it doesn't stop. I didn't change my service method because if there is an undefined `next` field it shouldn't be called either way right? Anyway, I updated the question with my service code. – Manuel Brás May 11 '21 at 20:25
  • Your service code looks fine. I think I forgot to save my stackblitz after I added `takeWhile`. Could you check it again? https://stackblitz.com/edit/angular-1zitjd?file=src%2Fapp%2Fapp.component.ts `takeWhile` should get you out of the infinite loop – eko May 11 '21 at 20:28
  • @eko That did the trick! Thank you for taking the time :-) – Manuel Brás May 11 '21 at 20:41
  • Glad I could help :-) I will provide it as an answer then – eko May 11 '21 at 20:42

1 Answers1

1

You can use the expand operator to recurse the link list and fetch the data sequantially/recursively.

Main code:

ngOnInit() {
  this.apiManager
    .fetchShopOrders()
    .pipe(
      expand(res => {
        if (res.results) {
          this.results.push(...res.results);
        }

        if (res.next) {
          return this.apiManager.fetchNextSetOfOrders(res.next);
        }

        return of(EMPTY);
      }),
      takeWhile(res => !!res.next) // take while res.next is defined
    )
    .subscribe(console.log);
}

Stackblitz demo

eko
  • 39,722
  • 10
  • 72
  • 98
  • Just one more thing. I also need to reset the results array because of the timer I set (I'm refreshing the list periodically). How would I go about doing that in this case? Note that `res` also returns a `previous` field to access the previous page of orders and if this field is null we're at the head of the list. – Manuel Brás May 12 '21 at 08:21
  • 1
    Hey! you can add `tap(()=> this.results = []), expand(..` or something like that to the beginning of your pipe. So every time a new timer starts, it will first set the results to an empty array. Tell me if that works. – eko May 12 '21 at 08:46