vdshb
vdshb

Reputation: 1999

Infinite loop while use function with http-call in *ngFor with async pipe

I call function in *ngFor statement:

@Component({
  selector: 'foo-view',
  template: '<div *ngFor="let foo of loadAll() | async"></div>'
})
export class FooComponent {

  loadAll() : Observable<Foo[]> {
    return this.http.get(`api/foos`)
      .map(response => response.json() as Foo[]);
  }

}

When code starts, it sends http requests in infinite loop over and over again.

Why? What can I do to avoid this?

P.S. I know standard workaround like

@Component({
  selector: 'foo-view',
  template: '<div *ngFor="let foo of foos"></div>'
})
export class FooComponent implements OnInit {

  foos: Foo[] = [];

  ngOnInit() {
    loadAll().subscribe(foos => this.foos = foos);
  }

  loadAll() : Observable<Foo[]> {
    return this.http.get(`api/foos`)
      .map(response => response.json() as Foo[]);
  }

}

but I am looking for the way to drop excess variable.

Upvotes: 5

Views: 3673

Answers (1)

martin
martin

Reputation: 96939

It's not an infinite loop. Every time Angular runs the change detector to check if any of bindings have changed it needs to run the loadAll() method which makes the HTTP call. This is because it can't be sure it hasn't changed single it last checked. You obviously don't want this. How often it needs to check for changes might very likely depend on other components as well (its parent for example).

One way to avoid this is exactly what you showed by creating property foos: Foo[].

If you don't want to use another state variable you could make an Observable chain that replays the cached data:

private cached;

ngOnInit() { 
  this.cached = this.http.get(`api/foos`)
    .map(response => response.json() as Foo[])
    .publishReplay(1)
    .refCount()
    .take(1);
}

And then in your template you can use just:

<div *ngFor="let foo of cached | async"></div>

Now it'll make just one request at the beginning and every time anyone subscribes to it'll just replay the value and complete.

Also, since RxJS 5.4.0 you can use shareReplay(1) instead of .publishReplay(1).refCount().

By the way you can also change the Change Detection Strategy on the component with changeDetection property to manually run the change detection. See ChangeDetectionStrategy,

Upvotes: 6

Related Questions