2

In my angular 6 application i am having a product_id which is an array,

product_id: any = ["123", "456"];

ngOnInit :

  ngOnInit() {
    this.product_id.forEach(element => {
      this.httpClient.get('https://api.myjson.com/bins/hiolc').subscribe(res => {
        this.productList = [];
        res.products.forEach( item => {
          this.productList.push(item);
        });
      })
    })
    console.log(this.productList);
  }

Here in console.log(this.productList);, i need to have the expected result as,

   [{
      "product_name": "Product One",
      "product_information": {
        "template_details": [
          {
            "template_name": "RACE Template",
            "productProperties": [
              {
                "property_id": 2518427931,
                "property_name": "Length",
                "property_value": "12 cm"
              },
              {
                "property_id": 2621195440,
                "property_name": "Width",
                "property_value": "10 cm"
              },
              {
                "property_id": 2621195441,
                "property_name": "Height",
                "property_value": "20 cm"
              }
            ]
          }
        ]
      }
    },
    {
      "product_name": "Product Two",
      "product_information": {
        "template_details": [
          {
            "template_name": "RACE Template",
            "productProperties": [
              {
                "property_id": 2518427931,
                "property_name": "Length",
                "property_value": "15 cm"
              },
              {
                "property_id": 2621195440,
                "property_name": "Width",
                "property_value": "12 cm"
              },
              {
                "property_id": 2621195441,
                "property_name": "Size",
                "property_value": "Medium"
              }
            ]
          }
        ]
      }
    }]

But which gives empty array []..

How to wait for the service to complete and then store the data into productList..

I am in the need of forEach, this.product_id.forEach(element as because in my real application based on the foreach i will get the product list for each product id sent via url..

Kindly help me to store the data after completion of entire service based on forEach after all the product id sent then get the final product list..

Working stackblitz: https://stackblitz.com/edit/flatternstructure-dvzpjv

Once again i am making it clear that in my real application i need to pass the id to get the product value like https://api.myjson.com/bins/hiolc + element..

Also if i get the final productList then i need to do another functionality with the final productList so only i am expecting completed data in productList at last and not inside forEach..

Maniraj Murugan
  • 8,868
  • 20
  • 67
  • 116
  • I do not see you are using element variable while calling API. Is there any reason for having for loop on product_id ? You are also setting this.productList to empty array inside response, which means after every response it will clear the previous results. – Vikram Jain Dec 19 '18 at 09:49
  • `console.log(this.productList);` is logged after an **async** block, hence it's evaluated **BEFORE** the request takes place. You should log it **inside** the subscribe callback. – briosheje Dec 19 '18 at 09:49
  • Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – briosheje Dec 19 '18 at 09:50
  • @VikramkumarChhajer, I am in the need of foreach like that because in my real applicatio i have like that.. Also explained in the question.. – Maniraj Murugan Dec 19 '18 at 09:52
  • @briosheje, I need to get the final result if i give it inside it loops according to foreach count.. BUt also i need the service inside foreach as because in my real application i have like that.. – Maniraj Murugan Dec 19 '18 at 09:53
  • Also if i get the final ```productList``` then i need to do another functionality with the final ```productList``` so only i am expecting completed data.. – Maniraj Murugan Dec 19 '18 at 09:56
  • use `Promise` for that case, or register an event (a callback) or, eventually, use forkJoin: https://www.learnrxjs.io/operators/combination/forkjoin.html . You need to fire a callback once **every** request has been completed and **join** the results you received. – briosheje Dec 19 '18 at 09:57

7 Answers7

1

use rxjs.concat :

[edit]

available on stackblitz

...
import { concat } from 'rxjs';
import { map } from 'rxjs/operators';
...

ngOnInit() {
  this.productList = [];
  concat(
    this.product_id.map(
      element => this.httpClient.get('https://api.myjson.com/bins/hiolc')
    )
  ).pipe(
    map((res: any) => this.productList = this.productList.concat(res.products))
  ).subscribe(res => {
    console.log(res);
    console.log(this.productList);
  });
}

Hope it helps.

dun32
  • 708
  • 6
  • 9
0

Update:

This would work:

import { zip } from 'rxjs'
....
ngOnInit() {
  const obs = []
  this.product_id.forEach(element => {
    obs.push(this.httpClient.get('https://api.myjson.com/bins/hiolc'))
  })
  zip(...obs).subscribe(res => {
    res.forEach(r => this.productList.push(...r.products))
    console.log(this.productList)
  })
}

stackblitz: https://stackblitz.com/edit/flatternstructure-nk9yha

zmag
  • 7,825
  • 12
  • 32
  • 42
  • Read my edited question.. This is not solution in my case, i need to have finalresult ascompleted data outside ```foreach```.. – Maniraj Murugan Dec 19 '18 at 09:58
  • 1
    Please make console.log, ```this.productList = res console.log(JSON.stringify(this.productList));``` it gives ```product one``` two times.. – Maniraj Murugan Dec 19 '18 at 10:27
  • @undefined Finally I have understood what you want. [https://stackblitz.com/edit/flatternstructure-nk9yha](https://stackblitz.com/edit/flatternstructure-nk9yha) – zmag Dec 19 '18 at 10:36
0

You have two problems: you log that too soon, and you overwrite the array.

First, the log to soon. You fire off requests (ids.forEach(id => this.http....)). Then right after that, you do a console.log - but the results are not back yet. Requests haven't been even fired yet, likely. That's why you see the empty array. Move the console.log statement right under this.productList.push(...) statement, you'll see the thing.

The second issue is that you fire parallel requests and they both reset the product list with this line: this.productList = [];.

You want to do that only once.

A better version of this all would be:

ngOnInit() {
  forkJoin(product_ids) // fire them in parallel.
    .pipe(
       map(id => this.http.get('https://api.myjson.com/bins/hiolc' + id)),   // fetch the things
       map(items=> this.productList = this.productList.concat(items)),
    )
    .subscribe(() => console.log('Done, items:', this.productList));

Even better version would be:

 ngOnInit() {
    this.someService.getProductItems(productIds)
        .subscribe(productList => this.productList = productList);
}

Meaning, move your Http calls into a service, do all this stuff there, and keep your component cleaner and simpler.

Zlatko
  • 18,936
  • 14
  • 70
  • 123
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/185489/discussion-on-answer-by-zlatko-get-the-final-data-from-angular-service). – Bhargav Rao Dec 19 '18 at 14:00
0

why don't you create a real service as described in angular documentation ?

here you want to implement httpClient direclty within a component https://angular.io/tutorial/toh-pt4#subscribe-in-heroescomponent

i would suggest you to first create an Injectable service, that will be used in the component constructor like your httpClient

Then, I would get the data in the service, and subscribe to it.

andrea06590
  • 1,259
  • 3
  • 10
  • 22
0

Could you try this:

ngOnInit() {
    let counter = 0;
    this.productList = [];
    this.product_id.forEach(element => {
      this.httpClient.get('https://api.myjson.com/bins/hiolc').subscribe(res => {
        res.products.forEach( item => {
          this.productList.push(item);
        });
        counter = counter + 1;
        if(counter === this.product_id.length){
            console.log(this.productList);  
            // call your next operation here
        }
      })
    })
}
Vikram Jain
  • 376
  • 2
  • 12
  • In your solution, ```console.log(this.productList);``` gives four objects.. Means ```Product One``` and also ```Product Two``` occurs multiple (two) times.. – Maniraj Murugan Dec 19 '18 at 10:31
  • thats because I am not passing element id in API call. So API responding two objects in each call. So If i am calling API two times, i will be having 4 objects. You need to call API specific to particular id. – Vikram Jain Dec 19 '18 at 10:49
0

How about this? StackBlitz

import { forkJoin } from 'rxjs';

....

ngOnInit() {
    forkJoin(
      this.product_id.map(element => this.httpClient.get('https://api.myjson.com/bins/hiolc'))
    )
    .subscribe((res) => {
        this.productList = this.productList.concat(res);
        console.log(this.productList);
    });
}
ams
  • 449
  • 5
  • 10
  • I need to get only two objects ```Product One``` and ```Product Two``` in final result.. Your solution gives both two times.. Using foreach if service call is made then i need to get that json alone in single time and no duplicate values.. – Maniraj Murugan Dec 19 '18 at 12:25
  • @undefined I don't quite understand what you mean. There are two IDs so it will make two get requests, which will then be merged into one response. I updated my stackblitz. Check it out and tell me if one of the arrays suits your needs. – ams Dec 19 '18 at 15:19
0

This would works as well:

this.productList$ = from(this.product_id).pipe(
    mergeMap(() =>  this.httpClient.get('https://api.myjson.com/bins/hiolc')),
    scan((productList, item) => [...productList, item], [])
    tap((productList) => console.log(productList))
).subscribe(() => {}) // or async pipe in a template