6

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 ..

AngularDebutant
  • 1,436
  • 5
  • 19
  • 41
  • 1
    You can send over all products as a list/array of product ids then do the actual adding on the backend code – DDelgro Jul 11 '17 at 12:54
  • Unfortunately, I don't have access to backend.. – AngularDebutant Jul 11 '17 at 12:55
  • `productIds.forEach(productId => service.addProduct(productId)` not efficient way to do this. send all products to server in one request – Hadi Farhadi Jul 11 '17 at 12:56
  • 1
    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: https://stackoverflow.com/questions/25704745/how-do-i-sequentially-chain-promises-with-angularjs-q – qqilihq Jul 11 '17 at 12:57
  • 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. – DDelgro Jul 11 '17 at 13:01
  • @qqilihq I am using ngrx observable. I am trying not to use promises. – AngularDebutant Jul 11 '17 at 13:01
  • 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. – infamoustrey Jul 11 '17 at 13:05
  • 1
    @infamoustrey Or just shove them into an array, by `push()`ing into is and `shift()`ing it. If the queue is clean, it stops. – Ismael Miguel Jul 11 '17 at 13:35

3 Answers3

7

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

CozyAzure
  • 8,280
  • 7
  • 34
  • 52
3

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())
Saravana
  • 37,852
  • 18
  • 100
  • 108
0

You can use the Observable.merge(). Try something like that

addProduct(productId):Observable<Response> {
   return this.http.post('someUrl', productId);
}

addAllproducts(productsIds) {
   let productedsObservable:Observable<Response>[]=[];
   for(let productID in productsIds){
    productedsObservable.push(this.addProduct(productID));
   }
   return merge(productedsObservable)
}

You need to subscribe to the requested function for it the execute the HTTP request. You can read more about combination operators (like merge) here

AFetter
  • 3,355
  • 6
  • 38
  • 62
Gili Yaniv
  • 3,073
  • 2
  • 19
  • 34