Angular pipe validation rules that enforce naming conventions, decoration requirements, and purity best practices for custom pipes.
Enforces naming conventions for Angular pipes with specified prefixes.
/**
* Enforces naming conventions for Angular pipes
* Ensures pipes follow consistent naming patterns with required prefixes
*/
export class PipePrefixRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
TSLint configuration:
{
"rules": {
"pipe-prefix": [true, "app", "custom"]
}
}Good pipe names:
@Pipe({
name: 'appFormatCurrency' // ✅ Has 'app' prefix
})
export class FormatCurrencyPipe implements PipeTransform {
transform(value: number): string {
return `$${value.toFixed(2)}`;
}
}
@Pipe({
name: 'customDateFormat' // ✅ Has 'custom' prefix
})
export class DateFormatPipe implements PipeTransform {
transform(value: Date, format: string): string {
// Custom date formatting logic
return value.toLocaleDateString();
}
}Bad pipe names:
@Pipe({
name: 'formatCurrency' // ❌ Missing required prefix
})
export class FormatCurrencyPipe implements PipeTransform {
transform(value: number): string {
return `$${value.toFixed(2)}`;
}
}Prevents creation of impure pipes that can impact performance.
/**
* Prevents creation of impure pipes
* Impure pipes execute on every change detection cycle and can impact performance
*/
export class NoPipeImpureRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Bad impure pipe:
@Pipe({
name: 'appFilter',
pure: false // ❌ Impure pipe
})
export class FilterPipe implements PipeTransform {
transform(items: any[], filter: any): any[] {
return items.filter(item => /* filtering logic */);
}
}Good pure pipe (default):
@Pipe({
name: 'appFilter' // ✅ Pure by default
})
export class FilterPipe implements PipeTransform {
transform(items: any[], filter: any): any[] {
return items.filter(item => /* filtering logic */);
}
}
@Pipe({
name: 'appFormat',
pure: true // ✅ Explicitly pure
})
export class FormatPipe implements PipeTransform {
transform(value: string): string {
return value.toUpperCase();
}
}Enforces use of @Pipe decorator on pipe classes.
/**
* Enforces @Pipe decorator usage on pipe classes
* Ensures pipes are properly decorated for Angular to recognize them
*/
export class UsePipeDecoratorRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Good pipe with decorator:
@Pipe({ // ✅ Proper @Pipe decorator
name: 'appCapitalize'
})
export class CapitalizePipe implements PipeTransform {
transform(value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1);
}
}Bad pipe without decorator:
// ❌ Missing @Pipe decorator
export class CapitalizePipe implements PipeTransform {
transform(value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1);
}
}Enforces implementation of PipeTransform interface on pipe classes.
/**
* Enforces PipeTransform interface implementation on pipe classes
* Ensures pipes implement the required transform method with proper typing
*/
export class UsePipeTransformInterfaceRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Good pipe with interface:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'appReverse'
})
export class ReversePipe implements PipeTransform { // ✅ Implements PipeTransform
transform(value: string): string {
return value.split('').reverse().join('');
}
}Bad pipe without interface:
@Pipe({
name: 'appReverse'
})
export class ReversePipe { // ❌ Missing PipeTransform interface
transform(value: string): string {
return value.split('').reverse().join('');
}
}// ✅ Pure pipe - cached result, better performance
@Pipe({
name: 'appExpensive',
pure: true
})
export class ExpensivePipe implements PipeTransform {
transform(value: any): any {
// Expensive computation - result will be cached
return this.performExpensiveOperation(value);
}
}
// ❌ Impure pipe - runs on every change detection
@Pipe({
name: 'appExpensive',
pure: false
})
export class ExpensivePipe implements PipeTransform {
transform(value: any): any {
// This runs on every change detection cycle
return this.performExpensiveOperation(value);
}
}@Pipe({
name: 'appFormatDate'
})
export class FormatDatePipe implements PipeTransform {
transform(value: Date | string, format: string = 'short'): string {
if (!value) return '';
const date = typeof value === 'string' ? new Date(value) : value;
switch (format) {
case 'short':
return date.toLocaleDateString();
case 'long':
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
default:
return date.toString();
}
}
}Template usage:
<p>{{ user.birthDate | appFormatDate }}</p>
<p>{{ user.birthDate | appFormatDate:'long' }}</p>{
"rules": {
"pipe-prefix": [true, "app", "shared"],
"no-pipe-impure": true,
"use-pipe-decorator": true,
"use-pipe-transform-interface": true
}
}@Pipe({ name: 'appCurrency' })
export class CurrencyPipe implements PipeTransform {
transform(value: number, currency: string = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency
}).format(value);
}
}
@Pipe({ name: 'appTruncate' })
export class TruncatePipe implements PipeTransform {
transform(value: string, length: number = 100): string {
if (!value || value.length <= length) return value;
return value.substring(0, length) + '...';
}
}// ⚠️ Consider using component methods instead for filtering
@Pipe({
name: 'appFilter',
pure: false // Required for filtering dynamic data
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchTerm: string): any[] {
if (!items || !searchTerm) return items;
return items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}
}Better approach for filtering:
// Component method approach (better performance)
export class UserListComponent {
users: User[] = [];
searchTerm: string = '';
get filteredUsers(): User[] {
if (!this.searchTerm) return this.users;
return this.users.filter(user =>
user.name.toLowerCase().includes(this.searchTerm.toLowerCase())
);
}
}