Search code examples
angularangular-pipe

Using AsyncPipe programmatically - Angular 2


I'm using a component to render table like data, I give it the list of columns, the data, how does the data map to each column, and finally a list of pipes to apply to each column data.

So far so good, only problem is when one of those pipes is the async pipe...

After experimenting for some while, I found out that when the asyncpipe is used on the template the transform method gets called several times. However if I use it programmatically the transform method gets called just once (the time I invoke it).

I guess the reason it's called multiple times on template it's because it's an impure pipe, but how can I handle it programmatically?

Here's a plunker demonstrating what I just said:

@Injectable()
export class AsyncProvider {
  constructor() {}
  
  getById(id: number) {
    // Just some mock data
    return Observable.of({id, times_five: id*5}).delay(1000);
  }
}

@Component({
  selector: 'my-app',
  providers: [AsyncPipe, AsyncProvider]
  template: `
    <div>
      <p>Template async pipe</p>
      <p>{{ asyncObj | async | json }}</p>
      <hr>
      <p>Programmatically async pipe</p>
      <p>{{ asyncObjPiped | json }}</p>
    </div>
  `,
  directives: []
})
export class App {
  constructor(
    private _provider: AsyncProvider,
    private _async: AsyncPipe
  ) {
    this.asyncObj = this._provider.getById(123);
    this.asyncObjPiped = this._async.transform(this.asyncObj);
  }
}

EDIT: Because AsyncPipe performs a markForCheck() on a ChangeDetectorRef uppon receiving new values, I also tried the following:

export class App {
  constructor(
    private _provider: AsyncProvider,
    private _async: AsyncPipe,
    private _ref: ChangeDetectorRef,
  ) {
    this.asyncObj = this._provider.getById(123);
    this.asyncObjPiped = this._async.transform(this.asyncObj);
    
    setInterval(() => {
      this._ref.detectChanges();
    }, 1000);
  }
}

Without any success :(


Solution

  • After some struggling I managed to get some results, here it's what I had to do, for future reference.

    First method: plunker

    export class App {
      constructor(
        private _provider: AsyncProvider,
        private _async: AsyncPipe,
      ) {
        
        this.asyncObj = this._provider.getById(123)
        
        let processPipe = () => {
          this.asyncObjPiped = this._async.transform(new_asyncObj);
        }
        let new_asyncObj = this.asyncObj.finally(processPipe).repeat(2);
        processPipe();
      }
    }
    

    Note that a new variable (new_asyncObj) is needed because finally seems to return a new object rather than modifying the existing one. And the repeat(2) after the finally so it will unwrap the promise.


    Second method: plunker

    export class App {
      constructor(
        private _provider: AsyncProvider,
        private _async: AsyncPipe,
      ) {
        
        this.asyncObj = this._provider.getById(123)
        
        setInterval(() => {
          this.asyncObjPiped = this._async.transform(this.asyncObj);
        }, 500);
        
      }
    }
    

    Recalculate the pipe every 500 ms, simple and effective, even though I was expecting for something better.


    Final method: plunker

    export class App {
      constructor(
        private _provider: AsyncProvider,
        private _async: AsyncPipe,
        private _ref: ChangeDetectorRef,
        private zone: NgZone,
      ) {
        
        this.asyncObj = this._provider.getById(123)
          
        this.zone.onMicrotaskEmpty
          .subscribe(() => {
            this.asyncObjPiped = this._async.transform(this.asyncObj);
            this.asyncObjPiped = this._async.transform(this.asyncObj);
            this._ref.detectChanges();
          });
      }
    }
    

    Using NgZone and ChangeDetectorRef seems to work aswell, even thought some ugly hacking as calling AsyncPipe twice, to unwrap the value.


    Anyway, hope it can really help anyone who is getting frustrated at dealing with non pure pipes programmatically!

    Any suggestion or better answer still welcome!