A directive is a class that adds behavior to an element in the DOM. Angular has three kinds:
| Type | What it does | Example |
|---|---|---|
| Component | Owns a template and DOM subtree | @Component |
| Attribute | Changes behavior or appearance of the host element |
NgModel, custom @Directive
|
| Structural | Adds or removes elements from the DOM |
*ngIf, *ngFor — superseded by @if, @for in Angular 17+ |
1. Built-in attribute directives
Attribute directives do not alter the DOM structure — they change how the host element looks or behaves. Common built-in examples:
-
NgModel— two-way binding for form inputs (fromFormsModule) -
NgClass— conditionally apply CSS classes -
NgStyle— conditionally apply inline styles
These are covered in the forms and class/style binding tutorials.
2. Structural directives
Structural directives modify the DOM by adding or removing elements. Angular 17+ introduced built-in control flow syntax that replaces the need for them:
| Old (structural directive) | Modern (built-in control flow) |
|---|---|
*ngIf="condition" |
@if (condition) { } |
*ngFor="let x of list" |
@for (x of list; track x.id) { } |
*ngSwitch / *ngSwitchCase
|
@switch / @case
|
The old * syntax still works but requires importing NgIf, NgFor (or CommonModule) in the component's imports array. Prefer built-in control flow in new code.
Custom structural directives use <ng-template> and ViewContainerRef under the hood — the *directive syntax is just shorthand for <ng-template [directive]>. They are rarely needed in modern Angular and are not covered in depth here.
3. Custom attribute directive
A custom directive is a class decorated with @Directive. The selector can be any valid CSS selector — element, attribute, or class — but an attribute selector ([appMyDirective]) is the convention for directives (element selectors are reserved for components).
Mark it standalone: true (recommended), then add it to the consuming component's imports array.
import { Directive, ElementRef, inject } from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true,
})
export class HighlightDirective {
private el = inject(ElementRef);
}
<!-- consumer template -->
<p appHighlight>Hover over me</p>
// consumer component
@Component({
imports: [HighlightDirective],
...
})
Listening to host events
The host property in @Directive maps DOM event names to handler expressions. Use $event to receive the native event object.
@Directive({
selector: '[appHighlight]',
standalone: true,
host: {
'(mouseenter)': 'onEnter($event)',
'(mouseleave)': 'onLeave($event)',
},
})
export class HighlightDirective {
private el = inject(ElementRef);
onEnter(event: MouseEvent) {
this.el.nativeElement.style.backgroundColor = 'lightyellow';
}
onLeave(event: MouseEvent) {
this.el.nativeElement.style.backgroundColor = '';
}
}
@HostListener is the older decorator-based alternative and produces the same result:
@HostListener('mouseenter', ['$event'])
onEnter(event: MouseEvent) { ... }
Prefer the host property in modern code — it keeps all host bindings in one place and does not require a decorator import.
Directive inputs
Directives accept inputs from the template the same way components do. Use @Input() (decorator) or input() (signal, Angular 17.1+).
Explicit input
The directive attribute applies the directive; a separate binding sets the input by name.
@Directive({
selector: '[appHighlight]',
standalone: true,
host: {
'(mouseenter)': 'onEnter()',
'(mouseleave)': 'onLeave()',
},
})
export class HighlightDirective {
color = input('yellow'); // signal input, default 'yellow'
private el = inject(ElementRef);
onEnter() {
this.el.nativeElement.style.backgroundColor = this.color();
}
onLeave() {
this.el.nativeElement.style.backgroundColor = '';
}
}
<!-- directive applied with attribute; color passed separately -->
<p appHighlight [color]="'orange'">Hover me</p>
Simplified input — alias matching the selector
When the input's alias matches the selector name, the directive attribute itself carries the value. No separate attribute is needed.
color = input('yellow', { alias: 'appHighlight' });
// decorator style: @Input('appHighlight') color = 'yellow';
<!-- expression binding — evaluates 'orange' as a string expression -->
<p [appHighlight]="'orange'">Hover me</p>
<!-- static value — assigns the literal string without brackets -->
<p appHighlight="orange">Hover me</p>
The alias makes [appHighlight] double as both the directive selector and the input binding — one attribute does both jobs.
Service injection
Directives support dependency injection the same way components do. Use inject() in a field initializer (preferred) or constructor injection.
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class AuthService {
isAdmin = signal(false);
}
import { Directive, ElementRef, inject, effect } from '@angular/core';
import { AuthService } from './auth.service';
@Directive({
selector: '[appAdminOnly]',
standalone: true,
})
export class AdminOnlyDirective {
private auth = inject(AuthService);
private el = inject(ElementRef);
constructor() {
effect(() => {
// reacts automatically whenever isAdmin changes
this.el.nativeElement.style.display = this.auth.isAdmin() ? '' : 'none';
});
}
}
<button appAdminOnly>Delete user</button>
Constructor injection is also valid: constructor(private auth: AuthService) {}. inject() is preferred in modern code because it works in field initializers and keeps the constructor clean.
4. hostDirectives — applying a directive without an attribute
A component can attach directives to itself via hostDirectives in @Component. The directive is applied automatically — consumers do not need to add the attribute in the template.
import { Component } from '@angular/core';
import { HighlightDirective } from './highlight.directive';
@Component({
selector: 'app-card',
hostDirectives: [HighlightDirective],
template: `<div class="card"><ng-content /></div>`,
})
export class CardComponent {}
<!-- no [appHighlight] needed — it is already attached by the component -->
<app-card>Card content</app-card>
To expose the directive's inputs or outputs to the component's consumers, list them explicitly:
hostDirectives: [{
directive: HighlightDirective,
inputs: ['color'], // <app-card [color]="'blue'"> now works
}]
Top comments (0)