"During the constructor initialization, an Angular/TypeScript global variable that was assigned within an

Utilizing HTTP calls to my Web API to fetch 2 API keys for accessing another API.

These API keys are fetched using 2 functions: getApiKey() and getAppId()

Upon calling these functions within the constructor, the returned value, stored in a global variable, appears as undefined.

However, if I call them outside the constructor, everything functions correctly.

I prefer not to rely on global variables. Even when attempting to create a variable inside the getApiKey() or getAppid() function and assign it within the http.get call, the outcome still remains undefined.

It seems the issue lies in the asynchronous nature of http.get, but I am unsure how to address this and make it wait for the response.

Below is the provided code snippet:

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
      }
    });
}
}

Answer №1

The provided code is functioning correctly. While this question may resemble How do I return the response from an asynchronous call?, I will address it in the context of observables.

The primary issue lies in a misunderstanding of asynchronous code, particularly in the following snippet:

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 lines are executed in the specified sequence. Given that TypeScript/JavaScript is synchronous, the http.get(...) is called first, followed immediately by return this.appid;. At this point, this.appid is still undefined, reflecting the expected behavior.

To address this issue, you must return the result of http.get(...), which is accessible only once its .subscribe() function is invoked.

In scenarios involving two distinct http.get(...) calls, such as those for apiKey and appId, you can leverage Rx operators like Observable.zip() to synchronize their completion/emission of values.

To guide you towards resolving the issue and aligning your code with expectations, a snippet is provided:

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) => {
        // Displaying keys
        console.log(`apiKey: ${result[0].text()}`);
        console.log(`appId: ${result[1].text()}`);

        // Constructing 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' represents the RequestOptions object returned by 'getOptions' function
        console.log(options);

        // Request to search the recipe can be initiated here
      })
      .subscribe();
  }
}

new SearchRecipeDemo().init();

An alternate version of the code, demonstrating observable mock-ups, is available in this JSBin JSBin snippet for further exploration.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What are some ways to create a dynamic child server component?

Take a look at the following code snippet // layout.tsx export default function Layout({children}: any) { return <div> {children} </div> } // page.tsx export const dynamic = "force-dynamic"; const DynamicChild = dynamic( ...

What is the method for ensuring TypeScript automatically detects the existence of a property when an object is statically defined?

In my software, I have an interface that serves as a base for other types. To simplify things for this discussion, let's focus on one specific aspect. This interface includes an optional method called getColor. I am creating an object that implements ...

Restrict the loop in handlebarjs

If we are working in the context of C#, and we have a list of people like this: List<people> lst = new List<people>(); lst.add(new people{Name='mark'}); lst.add(new people{Name='james'}); lst.add(new people{Name='antho ...

Tips for including a set width on Y-Axis labels

How can I set a fixed width for y-axis labels? Is there a way to do this? like shown here yAxis: { labels: { style: { width: '30px', fontSize: '10px', textOverflow: 'none' } } }, ...

What is the reason behind the return type of 'MyType | undefined' for Array<MyType>.find(...) method?

Currently, I am in the process of developing an Angular application using TypeScript. As part of this project, I have defined several classes along with corresponding interfaces that align perfectly with their respective properties: Map: export class Map ...

Looking to utilize Axios in React to make API calls based on different categories upon clicking - how can I achieve this?

My current issue involves making an API call upon clicking, but all I see in my console is null. My goal is to have different API categories called depending on which item is clicked. const [category, setCategory] = useState(""); useEffect(() => { ...

Attempting to access a specific JSON key using Observables

Apologies for the question, but I'm relatively new to Typescript and Ionic, and I find myself a bit lost on how to proceed. I have a JSON file containing 150 entries that follow a quite simple interface declaration: export interface ReverseWords { id ...

Angular 2: Changing the name of a component

Looking for guidance on renaming a component in my Angular2 application. I've already updated all the necessary files - changed the file names and folder name, as well as made adjustments to specific files such as y.component.ts, app.routing.ts, and a ...

Using Angular, you can easily add a <div> dynamically to all elements that share a class

In order to implement a solution, I am tasked with adding a new div element with the class '.cart-list-value .delete-product', which will contain a background image, to all elements that have the class .cart-list-item. Although I successfully man ...

Angular is throwing an error stating that it is unable to access the 'name' property of an undefined object

While working on my Angular application, I encountered the following error message: " Cannot read property 'name' of undefined" https://i.stack.imgur.com/O3vlh.png I've been searching through my code but am unable to pinpoint the issue. T ...

Leveraging the power of both IntelliJ and AngularCLI 6 to effortlessly import libraries using their package names

My Angular CLI 6 project consists of two components: A library containing services and components A project that utilizes this library When integrating the library into the frontend project, I typically use: import { SomeLibModule } from "some-lib"; H ...

How to extract component prop types in Vue 3 with typescript for reusability in other parts of your application

When you specify the props under the "props:" key of a Vue component, Vue can already automatically determine their types, which is quite convenient. However, I am wondering if there is an utility type in Vue that can be used to extract the props' ty ...

Tips for utilizing the @Input directive within the <router-outlet></router-outlet> component

I am new to Angular 4 and recently discovered that in Angular, data can be passed from a parent component to a child component using @Input like this: <child [dataToPass]="test"></child> My question is, how can I achieve the same functionalit ...

Encountering a NextJS _app.tsx problem - error - Issue with ./pages/_app.tsx file: line 3

Having trouble creating a custom script for my NextJs Project. Here's the error log: Error - ./pages/_app.tsx:3:12 Syntax error: Unexpected token, expected "from" 1 | import React from 'react' 2 | import '../styles/globals.css&apos ...

What categories do input events fall into within Vue?

What Typescript types should be used for input events in Vue to avoid missing target value, key, or files properties when using Event? For example: <input @input="(e: MISSING_TYPE) => {}" /> <input @keypress="(e: MISSING_TYPE) = ...

Navigate between tabs with ease for children

Setting up my routes has been a bit challenging. I created a listRoutes in my app-routing.module.ts with some parameters. const listRoutes: Routes = [ { path: '', component: MlsComponent, }, { path: 'vente', compon ...

Tips on expanding properties for Material-UI components with Typescript?

My goal is to enhance the props of the Button component from Material-UI using typescript in order to pass additional props to its children. import { NavLink } from 'react-router-dom'; import { Button } from 'material-ui'; <Button ...

Learn how to display or conceal the HTML for 'Share this' buttons on specific routes defined in the index.html file

Currently, I am in the process of updating an existing Angular application. One of the requirements is to hide the "Share this buttons" on specific routes within the application. The "Share" module typically appears on the left side of the browser window a ...

Issues encountered when retrieving data with ReactiveForms

My current project involves gathering data using ReactiveForms. Here is the structure of my setup: Initially, I create a modal to gather the necessary data: async present(){ const modal = await this.modalController.create({ component: dataComponent, cs ...

Setting the initial state for your ngrx store application is a crucial step in ensuring the

I'm completely new to ngrx and I'm currently exploring how to handle state management with it. In my application, each staff member (agent) is associated with a group of customers. I'm struggling to define the initial state for each agent ob ...