Pipe any method in template - quick & easy

mateuszroszczyk

Mateusz Roszczyk

Posted on January 31, 2022

Pipe any method in template - quick & easy

Cover template found on r/MemeTemplatesOfficial

Hi! This is my first article ever, so let me start with something simple 😅

I've seen a lot of Angular Devs saying that calling methods inside templates should be avoided because of performance reasons. They're right, obviously, nevertheless using methods directly is quick and easy, so it'll always be tempting. Have you ever been in a situation where you knew that you're gonna use a certain method only once, in a specific component, and a thought of going through the whole pipe creation process was like "ooohhh, omg, such effort for one function call, seriously?!" (actually, it's not that much of an effort but enought to be a disturbance). Behold:

MethodPipe 😍

Assuming your transforming method is pure, let's just pass it to a generic pipe, shall we? That way we can reap benefits of both pipe performance and ease of method direct call.

Pipe's transform method implementation example:

transform<T, U>(value: T, method: (arg: T) => U): U {
  return method(value);
}
Enter fullscreen mode Exit fullscreen mode

Usage:

{{'test string' | method: toPipeOrNotToPipe}}
Enter fullscreen mode Exit fullscreen mode

I was trying to think of a better name for the pipe but eventually I came to a conclusion that this one reads quite well: "pipe 'test string' through method toPipeOrNotToPipe"

Ok, but do we really get the same performance benefits as in case of implementing a specific pipe from ground up?

Yes, passed method isn't being treated in any different way, so it's memoized as it should be. If that answer satisfies you and you trust me then you can as well stop reading right here, otherwise...

Performance test

I've created a fresh app using ng new command, removed default content and filled app.component with test content:

private iterations = 50;
private multiplier = 1000000000;

public toPipeOrNotToPipe = (input: string): string => {
  this.calculatePrimes(this.iterations, this.multiplier);
  return input.toUpperCase();
};

private calculatePrimes(iterations: number, multiplier: number): number[] {
  const primes = [];
  for (let i = 0; i < iterations; i++) {
    const candidate = i * (multiplier * Math.random());
    let isPrime = true;
    for (let c = 2; c <= Math.sqrt(candidate); ++c) {
      if (candidate % c === 0) {
        // not prime
        isPrime = false;
        break;
      }
    }
    if (isPrime) {
      primes.push(candidate);
    }
  }
  return primes;
}
Enter fullscreen mode Exit fullscreen mode

calculatePrimes is a slightly adjusted version of MDN Intensive Javascript

Html - 3 cases:

  1. {{ 'test string' }}
  2. {{ 'test string' | method: toPipeOrNotToPipe }}
  3. {{ toPipeOrNotToPipe('test string') }}

I've enabled Angular's dev tools:

// main.ts

platformBrowserDynamic().bootstrapModule(AppModule).then(moduleRef => {
  const applicationRef = moduleRef.injector.get(ApplicationRef);
  const componentRef = applicationRef.components[0];
  enableDebugTools(componentRef);
}).catch(err => console.error(err));
Enter fullscreen mode Exit fullscreen mode

This allowed me to use ng.profile.timeChangeDetection() inside browser's console and, well..., time change detection 😆

Results

Rendered content CD cycle time [ms]
{{ 'test string' }} 0.000926
MethodPipe 0.000842
function call 291.614000

As you can see, rendering a previously memoized result is even a little faster than simple interpolation. Why? I don't wanna guess, we'd have to look into Angular's guts :)

Annotations:

  1. The results don't take the initial render into account.
  2. The times presented in the table are the average of 10 measurements.

Summary

Make yourself comfortable with pipes and use them 😉

💖 💪 🙅 🚩
mateuszroszczyk
Mateusz Roszczyk

Posted on January 31, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related