Directives in Angular
Directives are classes that add behaviour to elements in Angular templates. They are one of Angular's most powerful features — they let you extend HTML with custom attributes and structural transformations.
Almost everything in Angular is a directive. Components are directives with a template. But the term "directive" usually refers to the two non-component forms: structural directives and attribute directives.
The Three Types of Directives
Type | Purpose | Examples |
|---|---|---|
Component | Directive with its own template | Any @Component class |
Structural | Changes the DOM layout by adding/removing elements | *ngIf, *ngFor, *ngSwitch, @if, @for |
Attribute | Changes the appearance or behaviour of an element | ngClass, ngStyle, custom highlight |
Built-in Structural Directives
Structural directives manipulate the DOM structure. They are prefixed with an asterisk *, which is syntactic sugar for <ng-template>.
import { Component } from '@angular/core';
import { NgIf, NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';
@Component({
selector: 'app-structural-demo',
standalone: true,
imports: [NgIf, NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault],
template: `
<!-- *ngIf -->
<p *ngIf="isLoggedIn">Welcome back!</p>
<p *ngIf="!isLoggedIn">Please log in.</p>
<!-- *ngFor -->
<ul>
<li *ngFor="let item of items; let i = index; trackBy: trackById">
{{ i + 1 }}. {{ item.name }}
</li>
</ul>
<!-- *ngSwitch -->
<div [ngSwitch]="status">
<p *ngSwitchCase="'active'">Active user</p>
<p *ngSwitchCase="'inactive'">Inactive user</p>
<p *ngSwitchDefault>Unknown status</p>
</div>
`,
})
export class StructuralDemoComponent {
isLoggedIn = true;
status = 'active';
items = [
{ id: 1, name: 'Angular' },
{ id: 2, name: 'React' },
{ id: 3, name: 'Vue' },
];
trackById(index: number, item: { id: number }) {
return item.id;
}
}
Built-in Attribute Directives
Attribute directives modify an element's appearance or behaviour without changing the DOM structure.
import { Component } from '@angular/core';
import { NgClass, NgStyle } from '@angular/common';
@Component({
selector: 'app-attribute-demo',
standalone: true,
imports: [NgClass, NgStyle],
template: `
<!-- ngClass — add/remove CSS classes -->
<div [ngClass]="{
'btn-primary': isPrimary,
'btn-disabled': !isEnabled,
'btn-large': size === 'large'
}">Button-like div</div>
<!-- ngStyle — set inline styles -->
<p [ngStyle]="{
'color': textColor,
'font-size.px': fontSize,
'font-weight': isBold ? 'bold' : 'normal'
}">Styled text</p>
`,
})
export class AttributeDemoComponent {
isPrimary = true;
isEnabled = true;
size = 'large';
textColor = '#007bff';
fontSize = 16;
isBold = true;
}
Creating a Custom Attribute Directive
Custom attribute directives let you package reusable DOM behaviour. Below is a simple highlight directive that changes the background colour when the user hovers over an element.
// highlight.directive.ts
import {
Directive,
ElementRef,
HostListener,
Input,
OnInit,
} from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true,
})
export class HighlightDirective implements OnInit {
@Input('appHighlight') highlightColor = 'yellow';
@Input() defaultColor = 'transparent';
constructor(private el: ElementRef) {}
ngOnInit() {
this.el.nativeElement.style.backgroundColor = this.defaultColor;
}
@HostListener('mouseenter')
onMouseEnter() {
this.el.nativeElement.style.backgroundColor = this.highlightColor;
}
@HostListener('mouseleave')
onMouseLeave() {
this.el.nativeElement.style.backgroundColor = this.defaultColor;
}
}
// Using the directive
@Component({
selector: 'app-items',
standalone: true,
imports: [HighlightDirective],
template: `
<p appHighlight="lightblue">Hover over me — turns blue</p>
<p appHighlight>Hover over me — turns yellow (default)</p>
<p [appHighlight]="activeColor" defaultColor="#eee">Dynamic color</p>
`,
})
export class ItemsComponent {
activeColor = '#90ee90'; // light green
}
The @Directive Decorator
Every directive is decorated with @Directive. The key property is selector, which is a CSS selector that tells Angular when to apply the directive.
Selector Pattern | Example | Matches |
|---|---|---|
Attribute (most common) | [appHighlight] | <div appHighlight> |
Attribute with value | [appColor="red"] | <div appColor="red"> |
Element | app-hero | <app-hero> |
CSS class | .special | <div class="special"> |
Combined | input[type=text] | <input type="text"> |
// Different selector styles
@Directive({ selector: '[appFocus]' }) // attribute
@Directive({ selector: '.draggable' }) // class
@Directive({ selector: 'app-icon' }) // element
@Directive({ selector: 'button[appRipple]' }) // compound
Directive Lifecycle Hooks
Directives share most of the same lifecycle hooks as components (minus the view-related ones like AfterViewInit).
import {
Directive,
OnInit,
OnChanges,
OnDestroy,
SimpleChanges,
Input,
} from '@angular/core';
@Directive({ selector: '[appTracker]', standalone: true })
export class TrackerDirective implements OnInit, OnChanges, OnDestroy {
@Input() trackId = '';
ngOnInit() {
console.log('Directive initialised, trackId:', this.trackId);
}
ngOnChanges(changes: SimpleChanges) {
if (changes['trackId']) {
console.log('trackId changed to:', changes['trackId'].currentValue);
}
}
ngOnDestroy() {
console.log('Directive destroyed');
}
}
Directives with HostBinding and HostListener
@HostBinding and @HostListener let a directive interact with its host element without injecting ElementRef directly.
import { Directive, HostBinding, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appToggleClass]',
standalone: true,
})
export class ToggleClassDirective {
@Input('appToggleClass') toggleClass = 'toggled';
@HostBinding('class.toggled')
isToggled = false;
@HostBinding('attr.aria-pressed')
get ariaPressedState() {
return this.isToggled.toString();
}
@HostListener('click')
onClick() {
this.isToggled = !this.isToggled;
}
}
<button appToggleClass>Toggle me</button> <button [appToggleClass]="'active'">Custom class toggle</button>
Exporting a Directive (exportAs)
If a directive exposes public methods or properties that the template needs, use exportAs to let templates reference the directive instance via a template variable.
@Directive({
selector: '[appCollapse]',
standalone: true,
exportAs: 'collapse', // <-- exported name
})
export class CollapseDirective {
isCollapsed = false;
toggle() { this.isCollapsed = !this.isCollapsed; }
expand() { this.isCollapsed = false; }
collapse(){ this.isCollapsed = true; }
}
<!-- #panel gets a reference to the CollapseDirective instance -->
<div appCollapse #panel="collapse">
@if (!panel.isCollapsed) {
<p>Collapsible content here</p>
}
</div>
<button (click)="panel.toggle()">Toggle Panel</button>
Summary
Components are directives with a template — the most common directive type.
Structural directives (*ngIf, *ngFor) change the DOM layout by adding/removing elements.
Attribute directives (ngClass, ngStyle, custom) modify appearance or behaviour.
@Directive({ selector }) defines when the directive applies.
Use ElementRef for direct DOM access; prefer HostBinding/HostListener for cleaner code.
Directive lifecycle hooks: ngOnInit, ngOnChanges, ngOnDestroy.
exportAs lets templates reference a directive instance via a template variable.
Angular 17+ @if / @for control flow blocks are preferred over *ngIf / *ngFor in new code.