From harness-claude
Creates custom Angular pipes for pure data transformations like truncation and byte formatting, injects services, and uses built-ins like async, date, currency for performant templates.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Create custom Angular pipes for pure data transformation and use built-in pipes correctly to keep templates declarative and performant
Provides expert Angular/TypeScript patterns for standalone components, signals, RxJS, NgRx state management, smart/dumb components, and performance.
Guides Angular development for SPAs: components, services, modules, routing, lazy-loading, forms, DI, RxJS, TypeScript, and CLI setup. For app creation and feature implementation.
Applies RxJS patterns in Angular: switchMap for HTTP requests, takeUntilDestroyed for cleanup, async pipe for templates, catchError for errors. For reactive data fetching, state management, and subscription handling.
Share bugs, ideas, or general feedback.
Create custom Angular pipes for pure data transformation and use built-in pipes correctly to keep templates declarative and performant
{{ item.name.length > 20 ? item.name.slice(0, 20) + '...' : item.name }}) with a readable pipeAsyncPipe to handle observable/promise subscriptions and change detection automaticallyPipeTransform interface and decorate with @Pipe({ name: 'myPipe', standalone: true }).pure: true is the default). Pure pipes only re-run when the input reference changes — this is a significant performance optimization.pure: false (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.inject() for locale-aware formatting, permission checks, or translation.| async in templates instead of manual subscriptions — it subscribes, unsubscribes on destroy, and triggers change detection when values emit.| date:'shortDate', | currency:'USD', | number:'1.2-2', | titlecase, | keyvalue built-ins before writing custom pipes.camelCase in the class, matching camelCase in the name field. Use verb-noun for transformation pipes (truncate, highlight, formatBytes).imports array of components that use it.// 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>
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).
AsyncPipe advantages over manual subscriptions:
subscribe() and unsubscribe() on the observable lifecycle tied to the componentmarkForCheck() on OnPush components when new values arrive — without this, OnPush components won't updateObservable and Promise transparentlyPipe 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');
});
});
https://angular.dev/guide/pipes