In Angular 4, the variable within the component is refreshed or updated only when the service is called for the second

Recently, I started working with the Angular framework and encountered an issue while developing a basic data retrieval feature from a component using a service. I had already set up a web service that returns specific data for an ID (wwid). The function called via the service (app.service) from the component (queueSearch.component) triggers a GET request. Although the data is successfully retrieved within the service, it doesn't update the variable associated with the component (waittime) immediately. Strangely, the variable only gets updated after a second call (requiring me to click the search button twice to reflect the change on the HTML). How can I ensure that it updates with a single click?

app.service.ts

searchWWID(wwid: number)
{
  console.log("from app service wwid: " + wwid.toString());
  this.http.get("http://localhost:3000/api/list/" + wwid).subscribe(Data =>{
     if(Data != null)
     {
       this.waitingTime = JSON.stringify(Data)
     }
     else
     {
       this.waitingTime = "";
     }
     //this gets updated instantaneously and prints 
     console.log(this.waitingTime);
    });
   return this.waitingTime;
  }
}

queueSearch.component.ts

searchWWID()
{
  if(isNaN(Number(this.wwid)))
  {
    console.log("not a number");
  }
 else
 {
   if(this.wwid.toString().length !== 8)
   {
     console.log("should be eight digits")
   }
   else
   { 
     //this(waittime) only gets updated after every second call to this function
     this.waitTime =  this.AppService.searchWWID(Number(this.wwid))
   }
  }
}

queueSearch.component.html

 <h1>{{title}}</h1>
 <div style ="text-align:center">
 <form>
 <input type="text" class="form-control" placeholder="enter wwid" 
 [(ngModel)]="wwid" name="wwid" id="searchWWID">
 <button class="btn btn-primary" id="submitButton" 
 (click)=searchWWID()>Submit</button>
 </form>
 </div>
 <h2>waiting time: {{waitTime}}</h2>

Desired Outcome: Update waittime in HTML instantaneously after calling searchWWID().

Current Outcome: waittime only updates after every second function call.

Answer №1

Initially, the issue with the Service call not functioning as expected is as follows:

class Service {
    constructor(private http: HttpClient) {}

    waitingTime: string;

    searchWWID(wwid: number): string
    {
        // 1. To begin, you create the request
        this.http
            .get('http://localhost:3000/api/list/' + wwid)
            .subscribe(Data => {
                if (Data != null) {
                    this.waitingTime = JSON.stringify(Data);
                } else {
                    this.waitingTime = '';
                }
                console.log(this.waitingTime);
            });
        // 2. After that, you return the waiting time
        return this.waitingTime;
        // 3. 500ms later...
        // 4. You actually receive the response and set `this.waitingTime`
        //    but unfortunately, you have already returned
        // 5. No worries: the next time you call this function,
        //    it will return the result obtained at "step 2" above
    }
}

Due to the asynchronous nature of the result, it is advisable to use a Promise or an Observable to return the result. Since the http.get function returns an observable, we will utilize Observable:

import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
import {map, tap} from 'rxjs/operators';

class Service {

    constructor(private http: HttpClient) {}

    searchWWID(wwid: number): Observable<string>
    {
        return this.http
            .get('http://localhost:3000/api/list/' + wwid)
            .pipe(
                // Use a `map` for data transformation
                map(data => data !== null ? JSON.stringify(data) : ''),
                // Utilize a `tap` for a side effect
                tap(idString => console.log(idString)),
            );
    }
}

Now that the service call returns Observable<string>, all we need to do is integrate it into the component code:

class Component {
    constructor(private appService: Service) {}

    wwid: string;

    searchWWID(): void
    {
        // Implement "early return" to eliminate additional nesting
        if (isNaN(Number(this.wwid))) {
            console.log('not a number');
            return;
        }

        if (this.wwid.toString().length !== 8) {
            console.log('should be eight digits');
            return;
        }

        this.appService
            .searchWWID(Number(this.wwid))
            .subscribe((result: string) => this.wwid = result);
    }
}

However, there is a slight issue with the above code. When using subscribe in your component code, it's essential to remember to unsubscribe at some point (particularly when the component is destroyed).

One approach to address this is by storing the subscription in a property and manually unsubscribing using the onDestroy hook:

import {OnDestroy} from '@angular/core';
import {Subscription} from 'rxjs';

class Component implements OnDestroy {
    constructor(private appService: Service) {}

    wwid: string;
    wwidSubscription: Subscription;

    searchWWID(): void
    {
        // Implement "early return" to eliminate additional nesting
        if (isNaN(Number(this.wwid))) {
            console.log('not a number');
            return;
        }
        if (this.wwid.toString().length !== 8) {
            console.log('should be eight digits');
            return;
        }
        this.wwidSubscription = this.appService.searchWWID(Number(this.wwid))
            .subscribe((result: string) => this.wwid = result);
    }

    ngOnDestroy() {
        this.wwidSubscription.unsubscribe();
    }
}

update

As suggested in the comments, since our operation is a "one-off thing" where we receive the response once, we can use take(1), which will automatically complete the observable and unsubscribe the subscribers.

Furthermore, methods in the Http module such as get, post, etc., automatically complete once the request is finished. Therefore, in this scenario, we may not need to take any specific action, depending on the extent to which you prefer to expose the service details in your component.

Answer №2

If you're working on the queueSearch.component.ts file, follow these steps:

 else { 
     // The waitTime variable only refreshes after every second call to this function
     this.waitTime =  this.AppService.searchWWID(Number(this.wwid));                           
      console.log(this.waitTime); // Verify if the value is being updated here.
// If it is, then it's an issue with dirty checking
// Include changedetectorref in your constructor and use that reference below.
 this.cdr.markForCheck();

}

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

How should I properly initialize my numeric variable in Vue.js 3?

Encountering an issue with Vue 3 where the error message reads: Type 'null' is not assignable to type 'number'. The problematic code snippet looks like this: interface ComponentState { heroSelected: number; } export default define ...

Angular 7: Child component's OnChanges not firing under specific circumstances

I incorporated a child component into my parent component. Initially, the Child component's onChanges method is triggered via ngOnInit. However, upon clicking the onClick function, the Child component's onChanges method fails to trigger. What cou ...

Pause and be patient while in the function that delivers an observable

I have a function that loads user details and returns an observable. This function is called from multiple places, but I want to prevent redundant calls by waiting until the result is loaded after the first call. Can anyone suggest how this can be accompli ...

What steps should I take to enable users of my library to customize component templates as needed?

I created a customized component to enhance the appearance of bootstrap form controls, intended for use in various projects. I am considering transforming this into a library (a separate npm package with its own @NgModule), but some projects may wish to mo ...

Creating a unique custom complex template typeahead implementation with Angular 2 and Ng2-Bootstrap

I encountered an issue with the ng2-bootstrap typeahead implementation and have created a plunker to demonstrate the problem. Check out the plunker example here <template #customItemTemplate let-model="item" let-index="index"> <h5>This is ...

What is the best way to showcase information about an item alongside its parent in Angular 2?

I have a webpage with multiple datasets listed, and I want the ability to click on one of them to show its details right below it. Additionally, I need the routing to change from '/datasets' to '/datasets/1234567' upon clicking. Althou ...

Attempting to execute "npm install" on the organization's system is resulting in the following error message

Working on an Angular project that was cloned from Git. Encountered an error while installing npm dependencies: npm ERR! code ECONNREFUSED npm ERR! errno ECONNREFUSED npm ERR! FetchError: request to https://registry.npmjs.org/@angular%2fmaterial fail ...

NestJS is having trouble importing generated types from the Prisma client

When working with Prisma in conjunction with NestJs, I encountered an issue after defining my model and generating it using npx prisma generate. Upon importing the generated type, I can easily infer its structure: import { FulfilmentReport, FulfilmentRepor ...

angular2, my service announcement is triggered repeatedly

Can someone explain this to me? Here is the scenario: I am running a model on the API server which takes 5-10 minutes to complete. So, I continuously poll the API server to check when the process is done and then trigger a snackbar notification in my code ...

Typescript or Angular 2 for Google Maps

Is there a way to integrate Google Maps Javascript API with Typescript or Angular 2? Although libraries like https://github.com/SebastianM/angular2-google-maps are available, they may not provide full support for features like Events and Places Libraries ...

Using Private and Protected Methods in Typescript with React: Best Practices

Currently, I am engrossed in developing a React application utilizing Typescript. In cases where a component needs to offer functionality through a reference, I typically incorporate a public method (public focus() : void {...}). However, I find myself pon ...

Resolve ESLint errors in _document.tsx file of next.js caused by Document<any> and ctx.renderPage = () with TypeScript usage

maxbause took the initiative to create a comprehensive boilerplate project for Next.js, complete with GraphQL and styled components in TypeScript. Check out the project here However, upon integrating ESLint into the project, I encountered several warning ...

Unable to access res.name due to subscription in Angular

Right now, I am following a tutorial on YouTube that covers authentication with Angular. However, I have hit a roadblock: The code provided in the tutorial is not working for me because of the use of subscribe(), which is resulting in the following messag ...

When using Material-UI with TypeScript, an error is thrown stating that global is not defined

After running the following commands: npm install --save react react-dom @material-ui/core npm install --save-dev webpack webpack-cli typescript ts-loader @types/react @types/react-dom I transpiled main.tsx: import * as React from "react"; import * as R ...

Displaying a component upon clicking a button in Angular 9

Within the navbar, there is a "+" button that triggers the display of the component. <app-navbar></app-navbar> Upon clicking the button, the <app-stepper></app-stepper> component should be shown. <app-navbar></app-navba ...

Modify the contents of an input box according to the values entered in other input boxes

I have a specific requirement that I am trying to illustrate with an example. In this example, there are three input fields. Two of the input fields are independent, while the third input field is dependent on the values entered in the other two fields. ...

Printing the HTML Template of a widget with multiple loops results in a blank first page being displayed

I have encountered an issue while working with a table and ng-repeat loops in my report widget. The table displays fine on the screen, but when I try to print it, there is always a blank page at the beginning. Interestingly, if I remove the second and thir ...

What is the best way to implement a dynamic templateUrl for an Angular 2 Component?

My goal is to dynamically load a component's templateUrl based on a value passed from the parent component. I understand that this can be achieved by using property binding to pass the value through @Input. Below, I have provided an example where the ...

What could be causing Angular to not show the outcome? Why is it that only the complete-handler is being triggered?

Just wanted to share my current setup: ASP.NET Core 8 Web API .NET Core 8 backend for frontend Angular SPA served inside a bff using typescript (ES2022) Request flow: -> SPA (Cookie) -> BFF (Token) -> API Struggling with the SPA to display or i ...

Form with Material-UI's FreeSolo AutoComplete feature

Visit this Sandbox for more details In the provided SandBox example, Material AutoComplete is being used as a multiple input with free options. The component is expected to return ["term1","term2","term3"] to Formik, and each string should be displayed as ...