How to make Cancellable api calls in loop in Angular?

40 Views Asked by At

I have a list containing parameters, such as:

[
    {a:1a,b:1b,c:1c},
    {a:2a,b:2b,c:2c},
    {a:3a,b:3b,c:3c}
]

For each parameter object, I aim to initiate three consecutive API calls, each with specific values. Subsequently, the program should wait for the calls to complete in sequence – API 1 called with values in 'a', API 2 with values in 'b', and API 3 with values in 'c'.

These calls should occur one after the other, and they must be cancelable upon user action from UI button.

For instance, if the user cancels the second call while the first one is running, the second call should be terminated. Upon completion of one call, the next call should proceed to the third one.

How can this be achieve ?

I tried using Rxjs, but was not sure now to implement cancelable action from UI

2

There are 2 best solutions below

0
possum On

You can use a combination of switchMap which makes thing cancelable and takeUntil with a "cancel" observable which can let you cut off an observable stream. For your parameters, you can loop over them and use a structure like this:

const URL1 = buildURL1FromParameters(/* parameters like {a:1a,b:1b,c:1c} */)
/* other urls... */

const calls$ = of({}).pipe(
      switchMap(data => {
        return this.http.get(URL1).pipe(
          map(first => {
            return {...data, first};
          }),
          takeUntil(this.cancelHttpCalls$)
        );
      }),
      switchMap(data => {
        return this.http.get(URL2).pipe(
          map(second => {
            return {...data, second};
          }),
          takeUntil(this.cancelHttpCalls$)
        );
      }),
      switchMap(data => {
        return this.http.get(URL3).pipe(
          map(third => {
            return {...data, third};
          }),
          takeUntil(this.cancelHttpCalls$)
        );
      })
  })
      

You can modify building that final object up as you need, but subscribing to calls$ (or converting to a Promise and awaiting it) will net you an object like {first: ..., second: ..., third: ...}

this.cancelHttpCalls$ should be an observable on your service and you can funnel clicks to call service.cancelHttpCalls$.next() which will cancel it.

Set it up or rechain as you like, (also add error handling) but that should get you going.

0
maxime1992 On

It depends what you want, your original post wasn't super clear.

Do you want the ability to cancel per row, or just globally?

Either way, here are the 2 solutions:

from(
  res.map((row) =>
    from([
      service1.post(`some-url`, row.a),
      service2.post(`some-url`, row.b),
      service3.post(`some-url`, row.c),
    ]).pipe(concatAll(), takeUntil(cancel$))
  )
).pipe(concatAll());
const res$ = from(
  res.flatMap((row) => [
    service1.post(`some-url`, row.a),
    service2.post(`some-url`, row.b),
    service3.post(`some-url`, row.c),
  ])
) .pipe(concatAll(), takeUntil(cancel$));

They'll give the same result if there's no cancellation, but the behaviour when cancelling is different. In the first one, if you cancel it'll cancel the row and move on to the next one, in the second one, it'll cancel everything and stop straight away.

You can see a live demo for both here: https://stackblitz.com/edit/rxjs-nycgvb?file=index.ts