Angular @Pipe Without PipeTransform
ID |
javascript.angular_pipe_transform_interface |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
angular, framework, reliability, types |
Description
Reports an Angular @Pipe class that does not declare implements PipeTransform:
// Bad — nothing checks the method below.
@Pipe({ name: "shorten", standalone: true })
export class ShortenPipe {
tranform(value: string): string { // typo; throws at render time
return value.slice(0, 10);
}
}
// Good
@Pipe({ name: "shorten", standalone: true })
export class ShortenPipe implements PipeTransform {
transform(value: string): string {
return value.slice(0, 10);
}
}
Rationale
A pipe is called by name from a template, so its contract is never checked at the call site — {{ value | shorten }} compiles whatever the class happens to contain.
The one place the contract can be checked is the class itself, and PipeTransform is the declaration that asks for that check: it makes the compiler verify that a transform method exists and has a usable signature.
Without it, a misspelled method name, a missing method, or a transform that does not take the piped value all compile cleanly and fail at render time — inside a template, with no line pointing back at the pipe.
Remediation
Declare the interface:
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({ name: "shorten", standalone: true })
export class ShortenPipe implements PipeTransform {
transform(value: string, max = 10): string {
return value.slice(0, max);
}
}
Notes
A pipe that extends another class is left alone: the base may itself be a pipe that declares the interface, and this rule reads one class at a time.
javascript.angular_lifecycle_interface makes the same argument for the eight ng* lifecycle hooks. It maps only those, so the pipe contract needs its own rule.