Search code examples
angularangular-pipe

Is there a way to convert an impure pipe to a pure one?


I'm new in Angular 7, but I've been programming AngularJS for a couple of years. My question is based on the fact that when doing an asynchronous task in a pipe (not necessarily an ajax call, it can be another asynchronous task), it must be declared impure.

According to Angular Docs:

Angular executes an impure pipe during every component change detection cycle. An impure pipe is called often, as often as every keystroke or mouse-move.

These are many calls, for example if you use the same pipe in a table of 50 rows or in a list, try placing a console.log and you'll see the enormous number of times each pipe is executed again and over again. An example of ajax call in an impure pipe:

import {Pipe, PipeTransform} from '@angular/core';
import {AnyService} from '../services/any.service';

@Pipe({
  name: 'anyAjaxCall',
  pure: false
})
export class AnyAjaxCallPipe implements PipeTransform {

  private isDataCached = false;
  private cachedData: string = null;

  constructor(private anyService: AnyService) {
  }

  transform(value: any): string {

    if (!this.isDataCached) {
      this.isDataCached = true;

      this.anyService
        .read(value)
        .subscribe((response: any) => {
          this.cachedData = response.data.name;
        }, (err: any) => {
          this.isDataCached = false;
          console.error(err);
        });
    }

    return this.cachedData;
  }

}

Knowing the above, is it possible to convert a pipe from impure to pure once the asynchronous task has been completed? I know there is the possibility of saving the result of the asynchronous operation in a variable as a cache and avoiding the execution many times (like the code example above), but I think it would be better in performance to tell Angular that I have already executed my asynchronous task and I don't want to run it again.

I'm not expert in frond-end stuff, so any advice is welcome.


Solution

  • Short answer: No, it's not possible to convert it from impure to pure. However, your example is very similar to that given on the official docs:

    import { HttpClient }          from '@angular/common/http';
    import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({
      name: 'fetch',
      pure: false
    })
    export class FetchJsonPipe implements PipeTransform {
      private cachedData: any = null;
      private cachedUrl = '';
    
      constructor(private http: HttpClient) { }
    
      transform(url: string): any {
        if (url !== this.cachedUrl) {
          this.cachedData = null;
          this.cachedUrl = url;
          this.http.get(url).subscribe(result => this.cachedData = result);
        }
    
        return this.cachedData;
      }
    }
    

    For what it's worth, however, depending on your use case, I would move that call into a service (in angular services are singletons) and share the result throughout the application. Later on in the docs when talking about why they no longer have a filter or order by pipe it does say:

    Any capabilities that you would have put in a pipe and shared across the app can be written in a filtering/sorting service and injected into the component.

    Again it depends on your use case, but I hope this helps.