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

8
  • 1
    You can send over all products as a list/array of product ids then do the actual adding on the backend code Commented Jul 11, 2017 at 12:54
  • Unfortunately, I don't have access to backend.. Commented Jul 11, 2017 at 12:55
  • productIds.forEach(productId => service.addProduct(productId) not efficient way to do this. send all products to server in one request Commented Jul 11, 2017 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: stackoverflow.com/questions/25704745/… Commented Jul 11, 2017 at 12:57
  • 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. Commented Jul 11, 2017 at 13:35

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you boss. I'll have a look at it and report back to you.
@AngularDebutant glad that I helped
Quick question: Why didnt you use take(productIds.length) instead of recursion?
@callback Yeah that would work too, but you still need a recursion. .take() is specifying the terminating condition of that recursion.
Yep, it will though save a couple of lines of code, and will make the code easy to understand IMO :)
|
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())

4 Comments

forkJoin won't do it. He wants all the calls to be sequentials, not parallel.
Arent you removing the productId in that last line? so if productId initially is 1, then it will be loop([]) ..
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.
Oh I see. Thank you sir.
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

5 Comments

This answer sounds great! I'll accept it if it works, because I like how it uses Observables operators.
What's that response type you have there?
Response is the object type that http request returns. You can read about it here angular.io/api/http/Response
Ok! Then it's <Response> not <response> : )
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).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.