17

I`m getting an endless loop when I try to bind a async function like this:

<tr *ngFor="let i of items">
     <td>{{myAsyncFunc(i) | async}}</td>
</tr>

this is the function:

private myAsyncFunc(i: string): Promise<string> {
        return Promise.resolve("some");
}

I'm doing something wrong? Or this is a bug?

Matías González
  • 1,366
  • 3
  • 15
  • 30

4 Answers4

27

You're returning a new Promise from myAsyncFunc(i: string) on every call, that's why you get an "endless loop". Try returning the same Promise instance ;-)

The "endless loop" is actually not a traditional endless loop but rather a side-effect of async pipe triggering a change detection cycle when its input Promise resolves. On this new change detection cycle, angular will call myAsyncFunc(i: string) and get a new Promise to observe, which then resolves the whole thing starts again.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Johannes Rudolph
  • 35,298
  • 14
  • 114
  • 172
5

If your async/observable requires you to pass a parameter (e.g., you are inside an ngFor loop) perhaps you can create a custom async pipe for that.

@Pipe({
  name: 'customPipe'
})
export class customPipe implements PipeTransform {

  constructor(private someService: SomeService) {}

  /**
   * 
   * @param id 
   */
  transform(id: number): Observable<boolean> {
    return this.someService.shouldShow(id);
  }

}

And in your template you can call your async pipe as:

<td>{{id | customPipe | async}}</td>
jp6rt
  • 94
  • 1
  • 4
1

A workaround would be to use Memoizee.

You can create a custom decorator to memoize your function as instructed here. After that you can just write your function as follows:

@memoize()    
private myAsyncFunc(i: string): Promise<string> {
        return Promise.resolve("some");
}

This caches the initially returned Promise and when the async pipe re-checks the Promise's status it will get the cached Promise not a new one.

Be aware of the caching effects of memoizing a function.

-1

You can check my blogpost on this specific topic, when it strikes our project consuming 5GB od browser RAM :)
It's here

Simplest way to heal this issue is (as already mentioned) don't use function returned promise direct in template: {{ getPromise(id) | async }} but store this promise in controller (.ts file) and refer to it in view.
In addition, this can be healed by changing change detection settings to push-pull, but in my opinion it's brings way more evil than good.

Tomas
  • 3,269
  • 3
  • 29
  • 48