From 6371f0beb1bb3567d36a742707de2e98184be655 Mon Sep 17 00:00:00 2001 From: SkyZeroZx <73321943+SkyZeroZx@users.noreply.github.com> Date: Sun, 31 May 2026 16:45:56 -0500 Subject: [PATCH] docs: add skills for Angular pipes --- skills/dev-skills/angular-developer/SKILL.md | 6 + .../angular-developer/references/pipes.md | 145 ++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 skills/dev-skills/angular-developer/references/pipes.md diff --git a/skills/dev-skills/angular-developer/SKILL.md b/skills/dev-skills/angular-developer/SKILL.md index 92cab078ce2..2c8a3a69398 100644 --- a/skills/dev-skills/angular-developer/SKILL.md +++ b/skills/dev-skills/angular-developer/SKILL.md @@ -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: diff --git a/skills/dev-skills/angular-developer/references/pipes.md b/skills/dev-skills/angular-developer/references/pipes.md new file mode 100644 index 00000000000..f3b50fb93a2 --- /dev/null +++ b/skills/dev-skills/angular-developer/references/pipes.md @@ -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: ` +

Date: {{ issuedOn | date: 'mediumDate' }}

+

Total: {{ amount | currency }}

+ `, +}) +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.