0

I'm using HTTP calls to my Web API to retrieve 2 API keys to use another API.

These API keys get retrieved through 2 functions: getApiKey() and getAppId()

When I call those functions inside the constructor the value returned by them, which is a global variable, is undefined.

When I call it outside of the constructor it works fine.

I don't want to use global variables, but when I try to create a variable inside the getApiKey() or getAppid() function body and assign it inside the http.get call, it also returns undefined.

I'm guessing this has to do with http.get being asynchronous, but I have no idea how to fix it/how to make it wait for the response.

Here's my code:

import { Component, OnInit } from '@angular/core';
import { Http, Headers, Response, RequestOptions } from '@angular/http';
import { Constants } from '../../utils/constants';
import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-recipes',
  templateUrl: './recipes.component.html',
  styleUrls: ['./recipes.component.css']
})
export class RecipesComponent {
  appid;  
  appkey;
  matchesList;  
  recipeSearchForm: FormGroup;
  notFoundError: boolean = false;

  constructor(private http: Http) {
    this.searchRecipeInit();    //undefined here

    this.recipeSearchForm = new FormGroup({
      recipeSearchInput: new FormControl()
    });
  }

  getApiKey(){
    this.http.get(Constants.GET_YUMMLY_APP_KEY, this.getOptionsSimple()).subscribe((res: Response) => {  
      this.appkey = res.text();
      console.log(this.appkey);   
    });
    return this.appkey;    
  }

  getAppId(){
    this.http.get(Constants.GET_YUMMLY_APP_ID, this.getOptionsSimple()).subscribe((res: Response) => {  
      this.appid = res.text(); 
      console.log(this.appid);      
    });
    return this.appid;    
  } 

  getSearchParams(){
    // get from search text field
    var str = this.recipeSearchForm.get('recipeSearchInput').value
    // split into words and add + in between
    if(str != null) {
      var correctFormat = str.split(' ').join('+');
      return correctFormat          
    }
    return str
  }

  getOptions(){
    var headers = new Headers();

    headers.append('Content-Type', 'application/json' );    
    headers.append('X-Yummly-App-Key',this.getApiKey());    
    headers.append('X-Yummly-App-ID',this.getAppId());

    let options = new RequestOptions({ headers: headers });

    return options;
  }

  getOptionsSimple(){
    var headers = new Headers();

    headers.append('Content-Type', 'application/json' ); 

    let options = new RequestOptions({ headers: headers });

    return options;
  }

  searchRecipe() {     
      // not undefined here
      this.http.get(Constants.GET_SEARCH_RECIPE+this.getSearchParams(), this.getOptions()).subscribe((res: Response) => {  
        this.matchesList = res.json().matches;

        console.log(this.matchesList);
        if(this.matchesList.length == 0){
          this.notFoundError = true;
        }
        else{
          this.notFoundError = false;
        }
      },
      (err) => {
        if(err.status == 400){
          // Bad Request
        }
        else if(err.status == 409){
          // API Rate Limit Exceeded
        }
        else if(err.status == 500){
          // Internal Server Error
        }
      });
  }

  searchRecipeInit() {     
    this.http.get(Constants.GET_SEARCH_RECIPE+"", this.getOptions()).subscribe((res: Response) => {  
      this.matchesList = res.json().matches;

      this.notFoundError = false;      
    },
    (err) => {
      if(err.status == 400){
        // Bad Request
      }
      else if(err.status == 409){
        // API Rate Limit Exceeded
      }
      else if(err.status == 500){
        // Internal Server Error
      }
    });
}
}
cartant
  • 57,105
  • 17
  • 163
  • 197
Axelle
  • 442
  • 1
  • 9
  • 24

1 Answers1

1

The code you've included works as expected. This question is possibly a duplicate of How do I return the response from an asynchronous call? , but since it's more about observables I'll answer it.

Your misunderstanding of asynchronous code is the main problem, and specifically code like the following

getAppId() {
    this.http.get(Constants.GET_YUMMLY_APP_ID, this.getOptionsSimple()) // 1
        .subscribe((res: Response) => {                                 // 
            this.appid = res.text();                                    // 3
            console.log(this.appid);                                    // 4
        });
    return this.appid;                                                  // 2
} 

The code is executed in the numbered order as labeled to the right. Since TypeScript/JavaScript is synchronous, it will fire the http.get(...) function, and then continue to the next line, which in this case is the return this.appid;. What value does this.appid have at this time? undefined, thus it's working as expected.

You'll have to return the result of the http.get(...), which isn't available until it's .subscribe() function is called.

Since you're relying on two separate http.get(...) calls, the one for the apiKey and one for the appId, you can utilize Rx operators to "wait" for both of them to finish/emit values. In this case, the one that comes to mind is the Observable.zip() function.

I've created a snippet that should guide you in the correct direction to get your code working as expected.

class SearchRecipeDemo {

  private getAppId() {
    return this.http.get(Constants.GET_YUMMLY_APP_ID);
  }

  private getApiKey() {
    return this.http.get(Constants.GET_YUMMLY_APP_KEY);
  }

  private init(): void {
    this.searchRecipe();
  }

  private getOptions() {
    return Rx.Observable.zip(getApiKey(), getAppId()).map((result) => {
        // Prints your keys
        console.log(`apiKey: ${result[0].text()}`);
        console.log(`appId: ${result[1].text()}`);

        // Create the RequestOptions
        let headers = new Headers();

        headers.append('Content-Type', 'application/json');
        headers.append('X-Yummly-App-Key', result[0].text());
        headers.append('X-Yummly-App-ID', result[1].text();

          const options = new RequestOptions({
            headers: headers
          });
          return options;
        });
    });

  private searchRecipe() {
    this.getOptions().map((options) => {
        // Options here is the RequestOptions object returned from the 'getOptions' function
        console.log(options);

        //Make your request to search the recipe here
      })
      .subscribe();
  }
}

new SearchRecipeDemo().init();

Have a look a slightly different version of the code in this JSBin JSBin snippet that mocks the observables.

Daniel B
  • 8,770
  • 5
  • 43
  • 76
  • Thank you for the detailed response! I understand most of it, the only thing I'm still don't quite understand is how I should bind my appkey and appid to the response of my http call in this scenario. Do I make the http calls inside the getOptions body? – Axelle Jan 25 '18 at 11:24
  • I've updated my answer with a slightly different code snippet that matches your app a bit more. I replaced the mocked observables with calls to the `getApiKey()` and `getAppId()` functions. Note that I've also changed those functions to return the observable from the `http.get()` call. I couldn't really figure out why you had the `getOptionsSimple()` so I omitted and refactored the code a bit. – Daniel B Jan 25 '18 at 11:56