AngularJSDirectives Overview

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>.

TS
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;
  }
}
Note
In Angular 17+, the new built-in \`@if\`, \`@for\`, and \`@switch\` control flow blocks replace the structural directive equivalents without needing any imports. See the Control Flow page for details.
Built-in Attribute Directives

Attribute directives modify an element's appearance or behaviour without changing the DOM structure.

TS
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.

TS
// 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;
  }
}

TS
// 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">

TS
// 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).

TS
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');
  }
}
Tip
Use \`ngOnDestroy\` to clean up subscriptions, timers, or event listeners you set up manually in the directive. Forgetting to unsubscribe is a common source of memory leaks.
Directives with HostBinding and HostListener

@HostBinding and @HostListener let a directive interact with its host element without injecting ElementRef directly.

TS
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;
  }
}

HTML
<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.

TS
@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; }
}

HTML
<!-- #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.