Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this method/gettor get called X times in an Angular 2 service

I am trying to bind a value in my HTML component to a property in a service. If the private variable is null then it needs to go off and get the value from an HTTP call.

However, I noticed that as soon as I bind to the property gettor in my HTML component then it fires multiple times.

I have simplified the example with: this Plunker

  get Item(): string {
      console.log("Item()", this._item);
      return this._item;
  }

Open the Console and observe the multiple outputs of "Item() Initiated". I would expect it to only be hit once. (the result is the same with a method instead of a gettor). If I use the Gettor in a click method it only fires once.

like image 426
Rodney Avatar asked Nov 25 '16 18:11

Rodney


2 Answers

This is because the value bound on the html has to be evaluated every time something "has changed" in the app so that if the value of the bound item has changed it will be reflected to the html, thus giving you this magical autoupdating binding. This is probably what you want in 90% of the cases, as you don't want to worry about notifying angular to update a value of a bound property.

That being said if you have some heavy logic in the getter, or you prefer to control when the value will be updated you can change the components changeDetectionStrategy to onPush like in this plunker.

@Component({
  selector: 'my-app',
  template: `
    <div><button (click)="_service.ShowItem()">Show Item</button></div>
    <div>{{_service.Item}}</div>
  `,
  providers: [ Service ],
  changeDetection: ChangeDetectionStrategy.OnPush
})

There is an excellent explanation of how angular2 change detection works by thoughtram

This is like saying, "do not check this component when doing change detection, I will let you know when to check it".

You then can use a service called ChangeDetectorRef to mark the component for change detection.

For http what you want to do is have a trigger to do the http call and then get the value you need from the response and "cache" it somewhere so that you can bind it to the UI. The trigger can be a variety of things depending on your case, eg. interval, recursion, button etc

like image 156
masimplo Avatar answered Oct 21 '22 05:10

masimplo


The accepted answer is awesome and explains everything really clearly. However, like he stated 90% of the time, you won't want to change the change detection method. So I have a simple, albeit ugly, work-around that I thought I would share when you are going up to the database to fetch some data and don't want to hit your API several times right in a row. It will return a null object until the data comes down. Make sure you use *ngIf on your template as needed.

private __itemHit: number = 0;
private __item: string;
get Item(): string {
    if(!this.__item && this.__itemHit == 0){
        this.__itemHit = 1;

        //Your data fetch is likely different than the way I do mine.
        apiService.Post(url, body, option).subscribe((response) => { this.__item = response.Result;});
        console.log("Item()", this._item);
    }
    return this._item;
}
like image 27
Peter Harrison Avatar answered Oct 21 '22 04:10

Peter Harrison