Angular Directives

Directives let you attach extra behavior to DOM elements, and Angular splits them into structural directives that reshape the DOM and attribute directives that modify an existing element.

What Directives Do

A directive is a class that runs logic against a host element in the template, without necessarily rendering its own markup the way a component does. In fact, every component is technically a directive with a template attached. Angular ships several built-in directives and lets you write your own.

Structural Directives

Structural directives change the shape of the DOM by adding, removing, or repeating elements entirely. They are traditionally written with a leading asterisk, like *ngIf and *ngFor, which is shorthand Angular expands into an <ng-template>. Modern Angular also offers built-in control flow blocks such as @if and @for that achieve the same result with cleaner syntax and better type-checking.

Structural directive: *ngFor rendering a list

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'app-task-list',
  standalone: true,
  imports: [CommonModule],
  template: `
    <ul>
      <li *ngFor="let task of tasks; index as i">
        {{ i + 1 }}. {{ task }}
      </li>
    </ul>
  `
})
export class TaskListComponent {
  tasks = ['Write report', 'Review PR', 'Deploy build'];
}

bootstrapApplication(TaskListComponent).catch((err) => console.error(err));

Attribute Directives

Attribute directives change the appearance or behavior of an element without adding or removing it from the DOM. NgClass and NgStyle are the most common built-ins, letting you toggle CSS classes or inline styles based on component state.

Attribute directive: NgClass toggling styles

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'app-status-badge',
  standalone: true,
  imports: [CommonModule],
  template: `
    <span [ngClass]="{ active: isActive, inactive: !isActive }">
      {{ isActive ? 'Active' : 'Inactive' }}
    </span>
  `
})
export class StatusBadgeComponent {
  isActive = true;
}

bootstrapApplication(StatusBadgeComponent).catch((err) => console.error(err));
DirectiveCategoryWhat it does
*ngIf / @ifStructuralAdds or removes an element based on a condition
*ngFor / @forStructuralRepeats an element once per item in a collection
ngSwitch / @switchStructuralRenders one of several branches based on a value
ngClassAttributeToggles one or more CSS classes
ngStyleAttributeSets one or more inline style properties

Writing a Custom Attribute Directive

You are not limited to the built-ins. A custom directive is just a class decorated with @Directive that injects ElementRef or listens for host events to change how an element behaves.

A custom highlight directive

import { Component, Directive, ElementRef, HostListener } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

@Directive({
  selector: '[appHighlight]',
  standalone: true
})
export class HighlightDirective {
  constructor(private el: ElementRef) {}

  @HostListener('mouseenter')
  onEnter() {
    this.el.nativeElement.style.backgroundColor = 'yellow';
  }

  @HostListener('mouseleave')
  onLeave() {
    this.el.nativeElement.style.backgroundColor = '';
  }
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [HighlightDirective],
  template: `
    <p appHighlight>Hover over this text to see the highlight directive in action.</p>
  `
})
export class AppComponent {}

bootstrapApplication(AppComponent).catch((err) => console.error(err));
  • Structural directives reshape the DOM tree; attribute directives only touch the element they sit on.
  • Only one structural directive can be applied per host element — combine *ngIf and *ngFor by wrapping one in an <ng-container>.
  • Custom directive selectors are usually written in square brackets, like [appHighlight], so they apply as an attribute rather than a new tag.
  • The newer @if / @for / @switch control-flow blocks do not require importing CommonModule at all.
Note: Behind the scenes, *ngIf="condition" desugars to <ng-template [ngIf]="condition">, which is why you will occasionally see ng-template referenced in compiler error messages even though you never typed it yourself.
Note: CommonModule must be in a standalone component's imports array to use *ngIf, *ngFor, ngClass, or ngStyle — a missing import here produces a template error claiming the directive is not a known attribute.

Exercise: Angular Directives

What distinguishes structural directives from attribute directives?