A "directive" in Angular is simply a class that attaches behavior to a DOM element — Angular's own *ngIf and [ngClass] are directives under the hood, and writing a custom one uses the exact same underlying mechanism.
The three categories of Angular directives
Components are technically directives with a template; structural directives (prefixed with *, like *ngIf and *ngFor) add or remove elements from the DOM entirely; attribute directives (like [ngClass] and [ngStyle]) change an existing element's appearance or behavior without adding or removing it.
Generating a custom directive with the CLI
ng generate directive highlight
A basic custom attribute directive
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
}
This text has a yellow background.
The selector [appHighlight] (square brackets, matching an attribute rather than an element name) is what lets the directive be applied to any existing element as a plain HTML attribute, rather than requiring its own custom tag the way a component does.
Responding to events inside a directive
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter(): void {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
@HostListener('mouseleave') onMouseLeave(): void {
this.el.nativeElement.style.backgroundColor = '';
}
}
@HostListener binds an event listener directly on the host element the directive is applied to — this is how a directive reacts to user interaction without needing the consuming template to wire up its own event bindings.
Accepting an input to configure the directive
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input('appHighlight') highlightColor = 'yellow';
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter(): void {
this.el.nativeElement.style.backgroundColor = this.highlightColor;
}
}
Custom color highlight.
Aliasing the @Input() to the same name as the directive's own selector (appHighlight) lets it be configured directly in the attribute itself, without a separate property binding — a common, more compact pattern for a directive with one primary configurable value.
Using Renderer2 instead of direct nativeElement manipulation
constructor(private el: ElementRef, private renderer: Renderer2) {}
@HostListener('mouseenter') onMouseEnter(): void {
this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', 'yellow');
}
Directly setting nativeElement.style works but bypasses Angular's abstraction layer — Renderer2 is the recommended approach for anything beyond a quick example, since it keeps the code compatible with server-side rendering and other rendering contexts where direct DOM access isn't safely available.
When a custom directive is the right tool, versus a component
A directive suits adding behavior or styling to an existing element without changing its structure (highlighting, tooltips, auto-focus, click-outside detection) — reach for a component instead once the feature needs its own template markup, since directives don't render their own view content.