Mateusz Roszczyk
Posted on January 31, 2022
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);
}
Usage:
{{'test string' | method: toPipeOrNotToPipe}}
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;
}
calculatePrimes
is a slightly adjusted version of MDN Intensive Javascript
Html - 3 cases:
{{ 'test string' }}
{{ 'test string' | method: toPipeOrNotToPipe }}
{{ 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));
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:
- The results don't take the initial render into account.
- The times presented in the table are the average of 10 measurements.
Summary
Make yourself comfortable with pipes and use them 😉
Posted on January 31, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.