Search code examples
angularpromiseangular-pipe

Angular Custom Pipe : Resolving promises won't work but calling AsyncPipe will


The goal I want to achieve is to have a pipe that would handle the following signature :

any | (() => any | Promise<any>)

So I wrote the following pipe.

public transform(varOrFunc: any | (() => any | Promise<any>)): any {
    if (typeof varOrFunc === 'function') {
        const result = varOrFunc();
        if (result.then) {
            return this.asyncPipe.transform(result);
        } else {
            return result;
        }
    } else {
        return varOrFunc;
    }
}

--

<my-cmp [isDisabled]="myVarOrFunc | varOrFunc></my-cmp> 

Unfortunately this doesn't work as expected.

But

public transform(varOrFunc: any | (() => any | Promise<any>)): any {
    if (typeof varOrFunc === 'function') {
        const result = varOrFunc();
        if (result.then) {
            return result;
        } else {
            return Promise.resolve(result);
        }
    } else {
        return Promise.resolve(varOrFunc);
    }
}

--

<my-cmp [isDisabled]="myVarOrFunc | varOrFunc | async ></my-cmp> 

Does work perfectely fine.

Do you have any idea why ?

Here is a working demo of the problem


Solution

  • You can't use the AsyncPipe to synchronously transform an asynchronous result. The way the async pipe works is by storing the promise and triggering a change detection when it's resolved, and caching the result. So the transform method of the async pipe will always return null the first time it's called.

    I think that the best way to achieve what your trying to do is by reimplementing some of the async pipe logic in your pipe :

    • Your pipe needs to be not pure so angular won't cache its result, which means you'll have to do the caching in the pipe to avoid performance issues.
    • Use a ChangeDetectorRef to trigger a detectChange when the promise resolves.

    for exemple :

    @Pipe({ name: "varOrFuncAsync", pure: false })
    export class VarOrFuncAsyncPipe implements PipeTransform {
    
      private lastResult: any = null;
      private lastParam:  any | (() => any | Promise<any>) = null;
    
      constructor(private _ref: ChangeDetectorRef) {}
    
      public transform(varOrFunc: any | (() => any | Promise<any>)): any {
        if (this.lastParam === varOrFunc) {
          return this.lastResult;
        }
        this.lastParam = varOrFunc;
    
        if (typeof varOrFunc === "function") {
          const result = varOrFunc();
          if (result.then) {
            this.lastResult = null;
            result.then(r => this.updateResult(r));
            return null;
          } else {
            this.lastResult = result;
            return result;
          }
        } else {
          this.lastResult = varOrFunc;
          return varOrFunc;
        }
      }
    
      updateResult(result: any) {
        this.lastResult = result;
        this._ref.detectChanges();
      }
    }