Chaining http calls in angular 2 in a for loop

14,750

Solution 1

First return the observable from your service method:

addProduct(productId) {
   return this.http.post('someUrl', ReqData).map(json).subscribe(doStuff);
}

And use a recursive function and call it in the subscribe callback for each of the items in your array:

let loop = (id: number) => {
  service.addProduct(id)
    .subscribe((result) => {
      // This logic can be modified to any way you want if you don't want to mutate the `producIds` array
      if (productIds.length) {
        loop(productIds.shift())
      }
    })
}

loop(productIds.shift())

Solution 2

How about some recursive calls using .expand()?

First, create a recursive function and map the data for recursive use:

const recursiveAddProduct = (currentProductId, index, arr)=>{
    return service.addProduct(currentProductId)
        .map((response)=>{
            return {
                data:response,
                index: index+1,
                arr:arr
            }
        })
};

Now, call it recursively in your component:

//productIds is an array of Ids    
//start of using the first index of item, where index = 0

let reduced = recursiveAddProduct(productIds[0],0,productIds)
    .expand((res)=>{
        return res.index>res.arr.length-1 ? Observable.empty(): recursiveAddProduct(productIds[res.index],res.index,productIds)
    });

reduced.subscribe(x=>console.log(x));

Here is a working JSBin

Benefit of using .expand operator:

  1. You are still using Observables and can chain whatever operators you want to.
  2. You are calling one http after another, which is your requirement.
  3. You don't need to worry about error handling, they are all chained to a single stream. Just call a .catch to your observables.
  4. You can do anything to your recursion method (data manipulation,etc)
  5. You can set the condition when to terminate the recursion call.
  6. One-liner (almost) code.

Edit

You can use .take() operator to terminate your recursion, if you don't like the inline ternary, like this:

let reduced = recursiveAddProduct(productIds[0],0,productIds)
    .expand(res=>recursiveAddProduct(productIds[res.index],res.index,productIds))
    .take(productIds.length)

Working JSBin

Share:
14,750

Related videos on Youtube

AngularDebutant
Author by

AngularDebutant

Updated on June 04, 2022

Comments

  • AngularDebutant
    AngularDebutant almost 2 years

    I have some code that looks like

    //service.ts
    
    addProduct(productId) {
       this.http.post('someUrl', ReqData).map(json).subscribe(doStuff);
    }
    
    //component.ts
    
    addAllproducts(productsIds) {
       productIds.forEach(productId => service.addProduct(productId);
    }
    

    What I want is to be able to wait for each call to finish before calling for the next productId, without using window.setTimeout ..

    • DDelgro
      DDelgro almost 7 years
      You can send over all products as a list/array of product ids then do the actual adding on the backend code
    • AngularDebutant
      AngularDebutant almost 7 years
      Unfortunately, I don't have access to backend..
    • Hadi Farhadi
      Hadi Farhadi almost 7 years
      productIds.forEach(productId => service.addProduct(productId) not efficient way to do this. send all products to server in one request
    • qqilihq
      qqilihq almost 7 years
      Have a look at Angular's promises. This way you can chain your requests and run them sequentially. It's described here on SO in several questions, e.g. here: stackoverflow.com/questions/25704745/…
    • DDelgro
      DDelgro almost 7 years
      If you don't have access tot he backend then what @qqilihq suggested is spot on. You can do a loop once an asynchronous call/promise is fulfilled.
    • AngularDebutant
      AngularDebutant almost 7 years
      @qqilihq I am using ngrx observable. I am trying not to use promises.
    • infamoustrey
      infamoustrey almost 7 years
      Use a callback function, keep track of the number of productIDs that have been processed with a simple counter and in the call back, decrement the counter.
    • Ismael Miguel
      Ismael Miguel almost 7 years
      @infamoustrey Or just shove them into an array, by push()ing into is and shift()ing it. If the queue is clean, it stops.
  • CozyAzure
    CozyAzure almost 7 years
    forkJoin won't do it. He wants all the calls to be sequentials, not parallel.
  • AngularDebutant
    AngularDebutant almost 7 years
    Arent you removing the productId in that last line? so if productId initially is 1, then it will be loop([]) ..
  • Saravana
    Saravana almost 7 years
    I am passing the item removed from the array. There is an if check to stop the loop when all items have been processed. Like I said above you can implement that particular logic in many ways (using counters, etc). You can choose what you want.
  • AngularDebutant
    AngularDebutant almost 7 years
    Oh I see. Thank you sir.
  • AngularDebutant
    AngularDebutant almost 7 years
    Thank you boss. I'll have a look at it and report back to you.
  • AngularDebutant
    AngularDebutant almost 7 years
    This answer sounds great! I'll accept it if it works, because I like how it uses Observables operators.
  • AngularDebutant
    AngularDebutant almost 7 years
    What's that response type you have there?
  • Gili Yaniv
    Gili Yaniv almost 7 years
    Response is the object type that http request returns. You can read about it here angular.io/api/http/Response
  • AngularDebutant
    AngularDebutant almost 7 years
    Ok! Then it's <Response> not <response> : )
  • CozyAzure
    CozyAzure almost 7 years
    @AngularDebutant glad that I helped
  • callback
    callback almost 7 years
    Quick question: Why didnt you use take(productIds.length) instead of recursion?
  • CozyAzure
    CozyAzure almost 7 years
    @callback Yeah that would work too, but you still need a recursion. .take() is specifying the terminating condition of that recursion.
  • callback
    callback almost 7 years
    Yep, it will though save a couple of lines of code, and will make the code easy to understand IMO :)
  • CozyAzure
    CozyAzure almost 7 years
    @callback legit. Added the edit. I am just too used to ternary operator :)
  • Koushik Ravulapelli
    Koushik Ravulapelli over 5 years
    The above solution doesn't work for RxJS v5+ as the merge operator expects the comma separated values. So to make it work use return Observable.merge(...productedsObservable).