install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/angular-pipe-pattern" ~/.claude/skills/intense-visions-harness-engineering-angular-pipe-pattern-007a42 && rm -rf "$T"
manifest:
agents/skills/codex/angular-pipe-pattern/SKILL.mdsource content
Angular Pipe Pattern
Create custom Angular pipes for pure data transformation and use built-in pipes correctly to keep templates declarative and performant
When to Use
- Formatting data in templates (currency, date, truncation, pluralization) without component logic
- Reusing a display transformation across multiple templates
- Replacing complex template expressions (
) with a readable pipe{{ item.name.length > 20 ? item.name.slice(0, 20) + '...' : item.name }} - Using
to handle observable/promise subscriptions and change detection automaticallyAsyncPipe - Building pipes that inject services for locale-aware or permission-aware formatting
Instructions
- Implement the
interface and decorate withPipeTransform
.@Pipe({ name: 'myPipe', standalone: true }) - Keep pipes pure by default (
is the default). Pure pipes only re-run when the input reference changes — this is a significant performance optimization.pure: true - Mark a pipe
(impure) only when the output depends on something other than the input arguments (e.g., reading from a changing service or the current time). Impure pipes run on every change detection cycle.pure: false - Inject services into pipes via
for locale-aware formatting, permission checks, or translation.inject() - Use
in templates instead of manual subscriptions — it subscribes, unsubscribes on destroy, and triggers change detection when values emit.| async - Prefer
,| date:'shortDate'
,| currency:'USD'
,| number:'1.2-2'
,| titlecase
built-ins before writing custom pipes.| keyvalue - Name pipes with a consistent convention:
in the class, matchingcamelCase
in thecamelCase
field. Use verb-noun for transformation pipes (name
,truncate
,highlight
).formatBytes - Add the standalone pipe to the
array of components that use it.imports
// truncate.pipe.ts — pure transformation import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'truncate', standalone: true }) export class TruncatePipe implements PipeTransform { transform(value: string, limit = 100, suffix = '...'): string { if (!value) return ''; return value.length > limit ? `${value.slice(0, limit)}${suffix}` : value; } }
// format-bytes.pipe.ts — custom formatting with multiple args @Pipe({ name: 'formatBytes', standalone: true }) export class FormatBytesPipe implements PipeTransform { transform(bytes: number, decimals = 2): string { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`; } }
// highlight.pipe.ts — service-injected pipe import { Pipe, PipeTransform, inject } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; @Pipe({ name: 'highlight', standalone: true }) export class HighlightPipe implements PipeTransform { private sanitizer = inject(DomSanitizer); transform(text: string, search: string): SafeHtml { if (!search) return text; const regex = new RegExp(`(${search})`, 'gi'); const highlighted = text.replace(regex, '<mark>$1</mark>'); return this.sanitizer.bypassSecurityTrustHtml(highlighted); } }
<!-- Template usage --> <p>{{ product.description | truncate:150 }}</p> <p>{{ fileSize | formatBytes }}</p> <p [innerHTML]="product.name | highlight:searchTerm"></p> <li *ngFor="let user of users$ | async">{{ user.name }}</li> <p>{{ price | currency:'EUR':'symbol':'1.2-2' | uppercase }}</p>
Details
Pure vs impure pipes: Pure pipes cache their result for a given set of input references. If the input is the same object reference, Angular returns the cached result without calling
transform() again. This makes pipes very efficient with OnPush components. Impure pipes (pure: false) run on every change detection cycle — use sparingly and only when necessary (e.g., a pipe filtering a mutable array).
advantages over manual subscriptions:AsyncPipe
- Automatically calls
andsubscribe()
on the observable lifecycle tied to the componentunsubscribe() - Calls
onmarkForCheck()
components when new values arrive — without this,OnPush
components won't updateOnPush - Handles both
andObservable
transparentlyPromise
Pipe chaining: Pipes compose left to right:
{{ value | pipe1 | pipe2:arg }}. Each pipe's output is the next pipe's input.
Avoiding impure pipes for filtering/sorting: A common mistake is creating an impure pipe that filters or sorts an array. Because the array reference doesn't change when items are pushed, a pure pipe won't re-run. Better solutions: filter in the component with
computed() or a getter, or create a new array reference when the data changes.
Locale-aware built-in pipes:
DatePipe, CurrencyPipe, DecimalPipe, and PercentPipe all use Angular's locale system. Provide a locale with { provide: LOCALE_ID, useValue: 'de-DE' } to format numbers and dates for the user's locale automatically.
Testing pipes:
describe('TruncatePipe', () => { const pipe = new TruncatePipe(); it('truncates long strings', () => { expect(pipe.transform('a'.repeat(200), 50)).toBe('a'.repeat(50) + '...'); }); it('returns short strings unchanged', () => { expect(pipe.transform('short', 50)).toBe('short'); }); });
Source
https://angular.dev/guide/pipes
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.