Search code examples
angulartypescriptsubscription

How to wait until http.get is finished in angular


I've been trying to add values to my data array, however I came across the problem, that it goes to data.push() earlier than the value from getValue function is returned. I've been trying to fix it, but couldn't come up with the solution and feel kind of lost here.

this is the function where I form data:

formData() {
  for (const id of Object.keys(this.cols)) {
    let p= cols[id];
    switch (p.name) {
      case 'a':
      this.temp.name= this.addValue(m, p);
        break;
      case 'b':
        let idx = 1;
        let surname= this.getValue(idx);
        break;
    }
  }
  this.data.push(this.temp);
});

and this is how my getValue function looks like:

 getValue(id: string) {
let url = this.baseUrl + '/streams/' + id+ '/data?';
url += 'from=' + from + '&';
url += 'to=' + to;
this.http.get(url).map(r => r.json()).subscribe((response: any) => {
        if (response.data.values[0] !== null && response.data.values[0] !== undefined) {
         return response.data.values[0];
        }
}, (error: any) => {
    return null;
});

how can I avoid pushing data to array before that value is actually there?


Solution

  • If this were a less complicated question, I would fully support closing this question as a duplicate but, as my comment on the question says, it's a little more complicated that that.

    For starters, you should definitely read up on async functions and how to use responses from them. Next up, you should generally try to avoid using an async function inside of a loop as they can get difficult to predict and control. Fortunately, there are ways to get async functions to behave more syncronously.

    Observe the following:

    async formData(): Promise<void> {
      for (const id of Object.keys(this.cols)) {
        let p= cols[id];
        switch (p.name) {
          case 'a':
          this.temp.name= this.addValue(m, p);
            break;
          case 'b':
            let idx = 1;
            let surname = await this.getValue(idx);
            break;
        }
      }
      this.data.push(this.temp);
    });
    
    getValue(id: string): Promise<string> {
        return new Promise(resolve => {
            let url = this.baseUrl + '/streams/' + id+ '/data?';
            url += 'from=' + from + '&';
            url += 'to=' + to;
            this.http.get(url).map(r => r.json()).subscribe((response: any) => {
                if (response.data.values[0] !== null && response.data.values[0] !== undefined) {
                    resolve(response.data.values[0]);
                }
            }, (error: any) => {
                resolve(null);
            });
        });
    }
    

    Your call to getValue is an async call as it required a delay (the call to the server) before the value can be returned. I've wrapped this in a Promise as you wanted both success and error paths of the subscription to return a value.

    If we mark the formData method with the async keyword we can now use the await keyword within the method. This will cause the flow of the method to await the return from the getValue promise before control continues.

    Side-not: by marking the formData method with async, it makes the return-type of the method a Promise even though you aren't directly returning anything from the method.