Skip to content
Angular ng directives 4 min read

Directives Overview

A directive is a TypeScript class that attaches behavior to elements in the DOM. Whenever Angular renders a template, it scans the markup for selectors that match registered directives and lets each one extend, transform, or replace what the browser would otherwise show. Understanding the three kinds of directives — components, attribute directives, and structural directives — is the foundation for almost everything you build in Angular, because every component you write and every @if/@for block you reach for is a directive under the hood.

What is a directive?

At the language level, a directive is just a class decorated with @Directive (or @Component, which is a specialized directive). The decorator’s selector tells Angular which elements in a template the directive applies to, and the class body holds the logic, inputs, and outputs that run against each matched element. Angular instantiates one directive instance per matching element and wires it into change detection alongside the host element.

import { Directive, ElementRef, inject } from '@angular/core';

@Directive({
  selector: '[appHighlight]',
})
export class HighlightDirective {
  private el = inject(ElementRef<HTMLElement>);

  constructor() {
    this.el.nativeElement.style.backgroundColor = 'yellow';
  }
}

Apply it by placing the selector on any element:

<p appHighlight>This paragraph now has a yellow background.</p>

In modern Angular (17+), directives and components are standalone by default. You import a directive directly into a component’s imports array rather than declaring it in an NgModule.

The three types of directives

Angular groups directives into three categories based on what they affect: the whole element and its template, the element’s appearance or behavior, or the element’s presence in the DOM.

TypeDecoratorWhat it doesExamples
Component@ComponentA directive with its own template; renders UIEvery component you write
Attribute@DirectiveChanges the appearance or behavior of an existing elementngClass, ngStyle, ngModel
Structural@DirectiveAdds or removes elements from the DOM*ngIf, *ngFor (legacy); @if/@for (built-in)

Components

A component is the most common directive. It is defined with @Component, which is @Directive plus a template (and optional styles). Because it owns a template, a component renders a view rather than merely tweaking a host element.

import { Component } from '@angular/core';

@Component({
  selector: 'app-greeting',
  template: `<h2>Hello, {{ name }}!</h2>`,
})
export class GreetingComponent {
  name = 'DevCraftly';
}

Attribute directives

Attribute directives change how an element looks or behaves without changing the DOM structure. They are applied as attributes (hence the name) using a CSS attribute selector like [appHighlight]. Built-in examples include ngClass and ngStyle for conditional styling, and ngModel for two-way binding on form controls.

import { Directive, ElementRef, HostListener, inject, input } from '@angular/core';

@Directive({
  selector: '[appHover]',
})
export class HoverDirective {
  private el = inject(ElementRef<HTMLElement>);
  color = input('lightblue');

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

  @HostListener('mouseleave') onLeave() {
    this.el.nativeElement.style.backgroundColor = '';
  }
}
<button appHover color="gold">Hover me</button>

Structural directives

Structural directives shape the DOM by adding, removing, or repeating elements. Historically these used the asterisk syntax — *ngIf, *ngFor, *ngSwitch — which Angular desugars into an <ng-template>. Since Angular 17, the built-in control flow blocks @if, @for, and @switch cover the most common cases with better performance and no imports required.

<!-- Built-in control flow (Angular 17+) -->
@if (user(); as u) {
  <p>Welcome back, {{ u.name }}.</p>
} @else {
  <p>Please sign in.</p>
}

@for (item of items(); track item.id) {
  <li>{{ item.label }}</li>
}

You can still author your own structural directives with @Directive and TemplateRef/ViewContainerRef when you need reusable conditional rendering logic.

How Angular matches and runs directives

When a template compiles, Angular records every selector from the directives available to that component (its own imports plus inherited ones). At render time it matches each element against those selectors, instantiates the matching directives, resolves their inputs, and runs lifecycle hooks such as ngOnInit and ngOnChanges. Multiple directives can match the same element — a button can be a component host, carry ngClass, and respond to a custom attribute directive simultaneously.

Output:

[appHighlight] matched <p> → background set to yellow
[appHover]     matched <button> → mouseenter/mouseleave bound
@if            evaluated user() → rendered "Welcome back" branch

Best practices

  • Prefer the built-in @if/@for/@switch control flow over legacy *ngIf/*ngFor in new code — they are faster and need no imports.
  • Use attribute directives to encapsulate reusable behavior (tooltips, autofocus, permission checks) instead of duplicating logic across components.
  • Keep one responsibility per directive; compose multiple small directives on an element rather than building one that does everything.
  • Use inject() and signal input() in new directives for cleaner, tree-shakable code.
  • Use host metadata or @HostBinding/@HostListener instead of touching ElementRef.nativeElement directly when possible, to stay platform-agnostic.
  • Always track items in @for (or use trackBy with *ngFor) to avoid unnecessary DOM re-creation.
Last updated June 14, 2026
Was this helpful?