Anu
Anu

Reputation: 59

Variable in component gets updated only after second function call in service in angular 4

I am new to Angular framework. I was developing a basic data retrieval from component via service. I had already developed web service which spits out certain data for id(wwid). Function called via service(app.service) through component (queueSearch.component) initiates a GET request. The data is successfully retrieved within service however it does not update the variable associated with component (waittime) immediately. Variable only gets updated after second call (basically click the search button twice to update it on html). How could I update it in 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>

Expected result: Update waittime in html instantaneously after searchWWID() call.

Actual result: waittime is updated after every second function call

Upvotes: 1

Views: 1933

Answers (2)

Marian
Marian

Reputation: 4079

First, the reason the Service call is not working as you expect:

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

    waitingTime: string;

    searchWWID(wwid: number): string
    {
        // 1. first, 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. then you return the waiting time
        return this.waitingTime;
        // 3. 500ms later...
        // 4. you actually get the response and `this.waitingTime` is set
        //    but unfortunately you have already returned
        // 5. not to worry: the next time you call this function,
        //    it will return the result we have just received at "step 2" above
    }
}

Since you are dealing with an asynchronous result, you should use a Promise or an Observable to return the result. Considering that the http.get function returns an observable, we will use 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 transformation
                map(data => data !== null ? JSON.stringify(data) : ''),
                // use 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 use it in the component code:

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

    wwid: string;

    searchWWID(): void
    {
        // let's use "early return" to remove extra 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);
    }
}

There is a small problem with the above code, however. If you use subscribe inside your component code, you should remember to unsubscribe at some point (most likely when the component is destroyed).

One way to achieve that is to store the subscription in a property and manually unsubscribe 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
    {
        // let's use "early return" to remove extra 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", i.e. we get the response once, we can get away with using take(1), which will kindly complete the observable for us and unsubscribe the subscribers.

Moreover, the methods in the Http module such as get,post et al, automatically complete once the request has finished, so in this case we don't really have to do anything, depending on how much you prefer to leak the details of your service into your component.

Upvotes: 1

KiraAG
KiraAG

Reputation: 783

In your queueSearch.component.ts do this

 else { 
     //this(waittime) only gets updated after every second call to this function
     this.waitTime =  this.AppService.searchWWID(Number(this.wwid));                           
      console.log(this.waitTime); // check whether the value is getting update here .
// If yes, then its a matter of dirtyChecking
//Inject changedetectorref in your constructor an use that ref below.
 this.cdr.markForCheck();

}

Upvotes: 0

Related Questions