Reader Stacks

Angular Directives: Built-in Types and Creating a Custom Directive

Angular's own *ngIf and [ngClass] are directives too — the only difference between those and a hand-written custom one is who wrote the class, not what mechanism is at work.

Angular Directives: Built-in Types and Creating a Custom Directive

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.