Angular has three kinds of directives: components (a directive with a template), structural directives like *ngIf and *ngFor (which add or remove elements from the DOM), and attribute directives — the kind covered here — which change the appearance or behavior of an existing element without adding or removing it.
1. A basic highlight directive
ng generate directive highlight
import { Directive, ElementRef, inject } from '@angular/core';
@Directive({
selector: '[appHighlight]',
})
export class HighlightDirective {
private el = inject(ElementRef);
constructor() {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
}
<p appHighlight>This text has a yellow background.</p>
ElementRef gives direct access to the host DOM element — reaching for it directly like this (rather than through Angular's Renderer2) is fine for a build that only ever runs in a browser, but it bypasses Angular's platform abstraction, which matters if the app also needs to run server-side (SSR) or in a Web Worker, where nativeElement.style may not exist.
2. Reacting to events
import { Directive, ElementRef, HostListener, inject } from '@angular/core';
@Directive({
selector: '[appHighlight]',
})
export class HighlightDirective {
private el = inject(ElementRef);
@HostListener('mouseenter')
onMouseEnter(): void {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
@HostListener('mouseleave')
onMouseLeave(): void {
this.el.nativeElement.style.backgroundColor = '';
}
}
@HostListener attaches an event listener to whichever element the directive is applied to, without the directive needing to know that element's tag name or selector in advance — the same directive works identically on a <div>, a <p>, or a custom component.
3. Accepting an input
import { Directive, ElementRef, HostListener, Input, inject } from '@angular/core';
@Directive({
selector: '[appHighlight]',
})
export class HighlightDirective {
@Input('appHighlight') highlightColor = 'yellow';
private el = inject(ElementRef);
@HostListener('mouseenter')
onMouseEnter(): void {
this.el.nativeElement.style.backgroundColor = this.highlightColor;
}
}
<p [appHighlight]="'lightblue'">Custom color</p>
Naming the @Input the same as the directive's own selector (appHighlight) lets it be set inline on the same attribute that applies the directive, rather than requiring a second, separately-named binding — this is exactly the pattern ngModel and routerLink use internally.
4. When to reach for a directive instead of a component
If the goal is reusable DOM structure — markup with its own template — that's a component. If the goal is reusable behavior attached to markup that already exists elsewhere (a tooltip, a click-outside handler, an autofocus behavior, a permission-based show/hide), an attribute directive is the better fit: it composes onto any existing element instead of wrapping it in a new one.