Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 969
vdshb Avatar asked Jun 03 '17 15:06

vdshb


People also ask

Can we use async pipe in NgFor?

NgFor has a not-so-obvious feature that lets us will help us deal with asynchronous operations - the async pipe. The async pipe takes care of subscribing/unsubscribing to Observable streams for us.

How do you use async pipe with NgFor?

The async pipe allows us to subscribe to an Observable or Promise from the template and returns the value emitted. The async pipes subscribe to the observable when the component loads. It unsubscribes when the component gets destroyed.

When we use async pipe in angular?

The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes. When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks.

Does Async pipe automatically unsubscribe?

Try to avoid having to unsubscribe manually whenever possible. This can be achieved by using async pipes | , or any other RxJS operators like take and first that will automatically unsubscribe your observable under the hood.


1 Answers

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,

like image 161
martin Avatar answered Nov 03 '22 10:11

martin