Components & Templates

Angular Directives

Learn about Angular directives โ€” structural directives, attribute directives, and how to create custom directives to extend HTML behavior.

What Are Directives?

Directives are classes that add behavior to elements in Angular templates. There are three types:

TypePurposeExamples
ComponentDirective with a template@Component()
StructuralChange DOM layout (add/remove elements)@if, @for, *ngIf, *ngFor
AttributeChange appearance or behaviorngClass, ngStyle, custom directives

Built-in Control Flow (Angular 17+)

Modern Angular uses built-in control flow syntax directly in templates:

@if / @else if / @else

html
@if (user.role === 'admin') {
  <app-admin-panel />
} @else if (user.role === 'editor') {
  <app-editor-panel />
} @else {
  <app-viewer-panel />
}

@for with Track

html
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Price</th>
      <th>Stock</th>
    </tr>
  </thead>
  <tbody>
    @for (product of products; track product.id; let idx = $index; let isFirst = $first; let isLast = $last) {
      <tr [class.highlight]="isFirst">
        <td>{{ idx + 1 }}. {{ product.name }}</td>
        <td>{{ product.price | currency }}</td>
        <td>{{ product.stock }}</td>
      </tr>
    } @empty {
      <tr><td colspan="3">No products found.</td></tr>
    }
  </tbody>
</table>

Available context variables in @for:

VariableTypeDescription
$indexnumberCurrent index (0-based)
$firstbooleanTrue for the first item
$lastbooleanTrue for the last item
$evenbooleanTrue for even-indexed items
$oddbooleanTrue for odd-indexed items
$countnumberTotal items in the collection

@switch

html
@switch (order.status) {
  @case ('pending') {
    <span class="badge bg-yellow">โณ Pending</span>
  }
  @case ('shipped') {
    <span class="badge bg-blue">๐Ÿšš Shipped</span>
  }
  @case ('delivered') {
    <span class="badge bg-green">โœ… Delivered</span>
  }
  @default {
    <span class="badge bg-gray">Unknown</span>
  }
}

Legacy Structural Directives

These still work but the new control flow is preferred:

*ngIf

html
<div *ngIf="isVisible">Visible content</div>

<!-- With else -->
<div *ngIf="data; else loading">
  {{ data | json }}
</div>
<ng-template #loading>
  <p>Loading...</p>
</ng-template>

*ngFor

html
<li *ngFor="let item of items; let i = index; trackBy: trackById">
  {{ i + 1 }}. {{ item.name }}
</li>

[ngSwitch]

html
<div [ngSwitch]="color">
  <p *ngSwitchCase="'red'">Red selected</p>
  <p *ngSwitchCase="'blue'">Blue selected</p>
  <p *ngSwitchDefault>Other color</p>
</div>

To use legacy directives, import CommonModule or the specific directive.

Attribute Directives

ngClass

Dynamically set CSS classes:

html
<!-- Object syntax -->
<div [ngClass]="{
  'active': isActive,
  'disabled': isDisabled,
  'highlight': isHighlighted
}">
  Content
</div>

<!-- Array syntax -->
<div [ngClass]="['card', 'shadow', theme]">Content</div>

<!-- String expression -->
<div [ngClass]="getClasses()">Content</div>
typescript
export class AppComponent {
  isActive = true;
  isDisabled = false;
  isHighlighted = true;
  theme = 'dark';

  getClasses(): string {
    return this.isActive ? 'active card' : 'inactive card';
  }
}

ngStyle

Dynamically set inline styles:

html
<div [ngStyle]="{
  'background-color': bgColor,
  'font-size.px': fontSize,
  'padding.rem': padding
}">
  Styled content
</div>

<div [ngStyle]="getStyles()">Dynamic</div>

Creating Custom Attribute Directives

Custom directives let you encapsulate reusable behavior.

Highlight Directive

typescript
import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  standalone: true,
})
export class HighlightDirective {
  @Input() appHighlight = 'yellow';
  @Input() defaultColor = '';

  constructor(private el: ElementRef) {}

  @HostListener('mouseenter')
  onMouseEnter() {
    this.highlight(this.appHighlight || this.defaultColor || 'yellow');
  }

  @HostListener('mouseleave')
  onMouseLeave() {
    this.highlight('');
  }

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}

Usage:

html
<p appHighlight="lightblue">Hover to highlight blue!</p>
<p [appHighlight]="selectedColor">Dynamic color</p>
<p appHighlight>Default yellow highlight</p>

Auto-Focus Directive

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

@Directive({
  selector: '[appAutoFocus]',
  standalone: true,
})
export class AutoFocusDirective implements AfterViewInit {
  constructor(private el: ElementRef) {}

  ngAfterViewInit() {
    this.el.nativeElement.focus();
  }
}
html
<input appAutoFocus placeholder="I'm auto-focused!">

Tooltip Directive

typescript
import { Directive, Input, ElementRef, HostListener, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appTooltip]',
  standalone: true,
})
export class TooltipDirective {
  @Input('appTooltip') tooltipText = '';
  private tooltipElement: HTMLElement | null = null;

  constructor(
    private el: ElementRef,
    private renderer: Renderer2,
  ) {}

  @HostListener('mouseenter')
  show() {
    this.tooltipElement = this.renderer.createElement('span');
    this.renderer.appendChild(
      this.tooltipElement,
      this.renderer.createText(this.tooltipText),
    );
    this.renderer.addClass(this.tooltipElement, 'tooltip');
    this.renderer.appendChild(this.el.nativeElement, this.tooltipElement);
  }

  @HostListener('mouseleave')
  hide() {
    if (this.tooltipElement) {
      this.renderer.removeChild(this.el.nativeElement, this.tooltipElement);
      this.tooltipElement = null;
    }
  }
}
html
<button appTooltip="Click to save your changes">Save</button>

Creating Custom Structural Directives

typescript
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appRepeat]',
  standalone: true,
})
export class RepeatDirective {
  constructor(
    private templateRef: TemplateRef<unknown>,
    private viewContainer: ViewContainerRef,
  ) {}

  @Input() set appRepeat(times: number) {
    this.viewContainer.clear();
    for (let i = 0; i < times; i++) {
      this.viewContainer.createEmbeddedView(this.templateRef, {
        $implicit: i,
        index: i,
      });
    }
  }
}
html
<p *appRepeat="5; let i">Star {{ i + 1 }} โญ</p>

Directive vs Component

FeatureComponentDirective
Has templateโœ… YesโŒ No
Has selectorโœ… Yes (element)โœ… Yes (attribute)
Adds behaviorโœ… Yesโœ… Yes
Multiple per elementโŒ Oneโœ… Many
Use caseUI blocksBehavior/styling

Next Steps

Now that you understand directives for controlling DOM behavior, let's explore pipes โ€” Angular's tool for transforming data directly in templates.