Custom Directives & Pipes
Build your own reusable DOM behavior with a custom attribute directive, and your own display transformation with a custom pipe.
Building a Custom Attribute Directive
The @Directive decorator, combined with an ElementRef reference to the host element, lets you build reusable DOM behavior — like a highlight-on-hover effect.
import { Directive, ElementRef, HostListener, inject } from '@angular/core';
@Directive({ selector: '[appHighlight]', standalone: true,})export class HighlightDirective { private el = inject(ElementRef);
@HostListener('mouseenter') onMouseEnter() { this.el.nativeElement.style.backgroundColor = 'yellow'; }
@HostListener('mouseleave') onMouseLeave() { this.el.nativeElement.style.backgroundColor = ''; }}<p appHighlight>Hover over me!</p>Accepting Configuration via @Input
A directive can accept an @Input(), including one sharing its name with the directive's own selector, for configuration passed directly through the attribute.
@Directive({ selector: '[appHighlight]', standalone: true })export class HighlightDirective { private el = inject(ElementRef); @Input('appHighlight') color = 'yellow';
@HostListener('mouseenter') onMouseEnter() { this.el.nativeElement.style.backgroundColor = this.color; }}<p [appHighlight]="'lightblue'">Hover over me!</p>Building a Custom Pipe
The @Pipe decorator, combined with a transform method, lets you build your own reusable display transformation.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'truncate', standalone: true,})export class TruncatePipe implements PipeTransform { transform(value: string, maxLength: number = 50): string { if (value.length <= maxLength) return value; return value.slice(0, maxLength) + '…'; }}<p>{{ post.body | truncate:100 }}</p>Pure vs Impure Pipes
By default, a pipe is pure — it only re-runs when its input reference changes, exactly like a memoized function. Marking it impure (pure: false) makes it re-run on every change detection cycle, which is rarely needed and can hurt performance if overused.
@Pipe({ name: 'filterActive', standalone: true, pure: false })export class FilterActivePipe implements PipeTransform { transform(items: Item[]): Item[] { return items.filter(item => item.active); }}An impure pipe re-running on every check can be expensive for larger arrays — often, deriving the filtered result with a computed() signal in the component is a better-performing alternative.
FAQs
When the behavior is purely about modifying an existing element (styling, event handling) rather than rendering its own distinct markup — that's exactly what a component is for instead.
Yes — transform(value, param1, param2) accepts as many parameters as needed, each separated by a colon in the template: value | myPipe:param1:param2.
Summary
Custom directives extend DOM behavior, and custom pipes extend display transformations — both following the same decorator-based pattern as components and services. Next, you'll look at TypeScript patterns specific to Angular.