docs: add skills for Angular pipes

This commit is contained in:
SkyZeroZx
2026-05-31 16:45:56 -05:00
committed by Pawel Kozlowski
parent 6ac3e26fe0
commit 6371f0beb1
2 changed files with 151 additions and 0 deletions
@@ -82,6 +82,12 @@ When implementing dependency injection in Angular, follow these guidelines:
- **Injection Context**: Where `inject()` is allowed, `runInInjectionContext`, and `assertInInjectionContext`. Read [injection-context.md](references/injection-context.md)
- **Hierarchical Injectors**: The `EnvironmentInjector` vs `ElementInjector`, resolution rules, modifiers (`optional`, `skipSelf`), and `providers` vs `viewProviders`. Read [hierarchical-injectors.md](references/hierarchical-injectors.md)
## Pipes
When formatting values in templates, creating custom pipes, or reusing pipe-like logic in TypeScript, consult the following reference. Prefer pipes in templates; outside templates, avoid injecting pipe classes just to call `transform()`.
- **Pipes**: Built-in pipe imports, custom pipe naming and implementation, pure vs impure pipes, and TypeScript reuse patterns using standalone formatting functions or extracted plain functions. Read [pipes.md](references/pipes.md)
## Angular Aria
When building accessible custom components for any of the following patterns: Accordion, Listbox, Combobox, Menu, Tabs, Toolbar, Tree, Grid, consult the following reference:
@@ -0,0 +1,145 @@
# Pipes
Pipes transform data declaratively inside Angular templates using the `|` operator.
## Using pipes in templates
Import the pipe class and add it to the component's `imports` array.
```ts
import {Component} from '@angular/core';
import {DatePipe, CurrencyPipe} from '@angular/common';
@Component({
selector: 'app-invoice',
imports: [DatePipe, CurrencyPipe],
template: `
<p>Date: {{ issuedOn | date: 'mediumDate' }}</p>
<p>Total: {{ amount | currency }}</p>
`,
})
export class Invoice {
issuedOn = new Date();
amount = 49.99;
}
```
## Using pipe logic outside templates
**Do NOT inject pipe classes into services or other classes.** Pipes are template operators, not injectable services. Injecting them causes DI errors in standalone contexts and creates unnecessary coupling.
### Custom pipes — extract the transformation function
Extract the logic into a plain function. The pipe delegates to it; services import the function directly.
```ts
// kebab-case.ts
export function toKebabCase(value: string): string {
return value.toLowerCase().replace(/ /g, '-');
}
```
```ts
// kebab-case.pipe.ts
import {Pipe, PipeTransform} from '@angular/core';
import {toKebabCase} from './kebab-case';
@Pipe({name: 'kebabCase'})
export class KebabCasePipe implements PipeTransform {
transform(value: string): string {
return toKebabCase(value);
}
}
```
```ts
// formatter.service.ts — import the function, NOT the pipe
import {Injectable} from '@angular/core';
import {toKebabCase} from './kebab-case';
@Injectable({providedIn: 'root'})
export class FormatterService {
toSlug(title: string): string {
return toKebabCase(title);
}
}
```
### Built-in locale-aware pipes — use standalone formatting functions
`@angular/common` exports a standalone function for each locale-aware built-in pipe:
| Pipe | Standalone function |
| -------------- | ------------------- |
| `DatePipe` | `formatDate` |
| `CurrencyPipe` | `formatCurrency` |
| `DecimalPipe` | `formatNumber` |
| `PercentPipe` | `formatPercent` |
Inject `LOCALE_ID` to get the current locale and pass it to the function.
```ts
// CORRECT — use formatNumber instead of injecting DecimalPipe
import {Injectable, LOCALE_ID, inject} from '@angular/core';
import {formatNumber} from '@angular/common';
@Injectable({providedIn: 'root'})
export class PriceService {
private locale = inject(LOCALE_ID);
formatQuantity(value: number): string {
return formatNumber(value, this.locale, '1.0-0');
}
}
```
```ts
// WRONG — do not inject pipe classes
import {Injectable} from '@angular/core';
import {DecimalPipe} from '@angular/common';
@Injectable({providedIn: 'root'})
export class PriceService {
// ❌ DecimalPipe is not designed to be injected
private pipe = inject(DecimalPipe);
}
```
## Creating custom pipes
Use the Angular CLI to generate a pipe:
```bash
ng generate pipe path/to/my-pipe
```
A pipe needs a `@Pipe` decorator with a `name` and a `transform` method implementing `PipeTransform`.
```ts
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({name: 'truncate'})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit = 50): string {
return value.length > limit ? value.slice(0, limit) + '…' : value;
}
}
```
- **`name`**: camelCase. Do not use hyphens.
- **Class name**: PascalCase version of `name` with `Pipe` appended (e.g., `TruncatePipe`).
## Impure pipes
Mark a pipe `pure: false` only when you need to detect mutations inside arrays or objects. Impure pipes run on every change-detection cycle and can hurt performance.
```ts
@Pipe({name: 'filterItems', pure: false})
export class FilterItemsPipe implements PipeTransform {
transform(items: string[], query: string): string[] {
return items.filter((i) => i.includes(query));
}
}
```
IMPORTANT: Avoid impure pipes unless absolutely necessary.