Calling the transform method of an Angular Pipe in the component class code
Posted on
Target audience
This article is intended to help anybody that is looking for ways to call the transform
method of any Angular Pipe in a component class code or in other TypeScript code.
We will also learn about some other simple trick on how to design Angular pipes and how to test them in those particular cases.
Introduction
There are several cases where you would want to reuse the transform
functionality of a pipe in your component class code. The most simple example is formatting dates in an Angular component's template. The DatePipe
, provided by the Angular framework itself, is imported in the providers of the component and then used by piping the value in the template.
For example, {{currentDate | date : "fullDate"}}
would print out something like "Friday, September 29, 2023" in the template. If you would like to use the same functionality in the component class code, you can inject the DatePipe
and then call the transform
method manually.
Component example:
import { DatePipe } from '@angular/common';
@Component({
...
template: `{{currentDate | date : "fullDate"}}`
imports: [DatePipe],
standalone: true,
})
export class ExampleComponent {
constructor(public currentDate = new Date()) {
const formattedDate = inject(DatePipe).transform(this.currentDate, 'fullDate');
console.log(formattedDate);
}
}
Note: This is not the way I would personally recommend to reuse the functionality of any (Angular build-in) pipe. Continue reading to find out why there are better ways.
Injection Context
This method can only be used wherever you are in your code that has an Injection Context. You could use the runInInjectionContext
function to make sure you have access to the right providers, but still you would need access to the right injector.
Another way would be to manually instantiate the DatePipe
class. In that case you probably need the applicable LOCALE_ID
of your application, leading to again, needing the right injection context. 🤷🏼
A better way to reuse the functionality of a pipe
Angular build-in Pipes
Next to providing common functionality like formatting dates, percentages, currencies or numbers through pipes, Angular also exposes the inner working of those pipes directly as pure functions. For example, considering the use case from above, where we injected the DatePipe
to be able to use the transform
method, can be rewritten as follows:
Component example, with formatDate:
import { formatDate } from '@angular/common';
@Component({
...
})
export class ExampleComponent {
constructor(){
const formattedDate = formatDate(currentDate, 'fullDate');
console.log(formattedDate);
}
}
We don't even need to inject the DatePipe
anymore, because the formatDate
is exposed as a pure function directly. But we still need to get the applicable LOCALE_ID
somehow. So in this particular case, we can't get around needing the injection context.
Custom Pipes
When creating your own custom pipes for your applications and having the same need to reuse the functionality of the transform
method, it's better to start with an external function first. This way you can easily use and test this function on its own:
sum-of-numbers.ts
export function sumOfNumbers(listOfNumbers: number[]): number {
return listOfNumbers.reduce((a, b) => a + b, 0);
}
sum-of-numbers.spec.ts
describe('sumOfNumbers', () => {
it('should return the sum of all numbers in the list', () => {
const listOfNumbers = [1, 2, 3, 4, 5];
const result = sumOfNumbers(listOfNumbers);
expect(result).toBe(15);
});
});
sum-of-numbers.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'sumOfNumbers',
standalone: true,
})
export class SumOfNumbersPipe implements PipeTransform {
transform(listOfNumbers: number[]): number {
return sumOfNumbers(listOfNumbers);
}
}
// no need to test this pipe, as it's just a wrapper around the function
// and you would be testing instantiating a class, which is not necessary
Now that we have the function and its test covered, we can wrap it in a pipe. This way we can still use the function in our component class code, but also in our templates.
Example component:
import { SumOfNumbersPipe } from './sum-of-numbers.pipe';
import { sumOfNumbers } from './sum-of-numbers';
@Component({
...
template: `{{listOfNumbers | sumOfNumbers}}`
imports: [SumOfNumbersPipe],
standalone: true,
})
export class ExampleComponent {
constructor(public listOfNumbers = [1, 2, 3]) {
console.log(sumOfNumbers(this.listOfNumbers));
}
}
Conclusion
There is no wrong or right way of reusing the functionality of any (Angular build-in) pipe. It all depends on personal or project-specific preference and how your applications are structured. But at least now you know how I would do it, and I honestly believe it's a better way :). Thanks for reading!
Further reading
By reading this article I hope you can find a solution for your problem. If it still seems a little bit unclear, you can hire me for helping you solve your specific problem or use case. Sometimes even just a quick code review or second opinion can make a great difference.