# Directives

Course: [Angular](https://www.devtutorials.ai/learn/angular) · Section: Core Angular Concepts
Source: https://www.devtutorials.ai/learn/angular/core-concepts/directives

> What directives are, the structural directives *ngIf, *ngFor and *ngSwitch, the modern @if, @for and @switch blocks, the attribute directives ngClass and ngStyle, best practices, and a first custom directive.

## What Are Directives?

A **directive** is a class that adds behavior to an element in a template. Angular applies the directive wherever its selector appears in the HTML.

There are three kinds of directives:

- **Components.** Directives with their own template. Every component you have written so far is a directive.
- **Structural directives.** Add, remove or repeat elements in the page, such as `*ngIf` and `*ngFor`.
- **Attribute directives.** Change the appearance or behavior of an existing element, such as `ngClass` and `ngStyle`.

### Real-World Example

In an admin dashboard, a structural directive renders the Delete button only for users with the `admin` role, and another repeats one table row for each record returned by the API.

An attribute directive highlights overdue invoices in red, and a custom `appAutofocus` directive puts the cursor in the search box when the page opens.

<figure className="ngc-fig">
<svg viewBox="0 0 640 205" role="img" aria-label="Three kinds of directives: components with their own template, structural directives that add or remove elements, and attribute directives that change look or behavior.">
<defs><marker id="ngc-d1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" className="ngc-arrowhead" /></marker></defs><g className="ngc-node ngc-solid c-red"><rect x="240" y="14" width="160" height="46" rx="9" /><text x="320.0" y="35" textAnchor="middle" style={{fontSize:"17px"}}>Directive</text><text className="ngc-sub" x="320.0" y="53" textAnchor="middle">adds behavior</text></g><line className="ngc-link ngc-flow c-blue" x1="320" y1="62" x2="110" y2="110" markerEnd="url(#ngc-d1)" /><g className="ngc-node ngc-soft c-blue ngc-pop ngc-p1"><rect x="20" y="112" width="180" height="56" rx="9" /><text x="110.0" y="137" textAnchor="middle" style={{fontSize:"15px"}}>Component</text><text className="ngc-sub" x="110.0" y="155" textAnchor="middle">has its own template</text></g><text className="ngc-note" x="110" y="192" textAnchor="middle" style={{fontFamily:"var(--mono)",fontSize:"12px"}}>&lt;app-card&gt;</text><line className="ngc-link ngc-flow c-purple" x1="320" y1="62" x2="320" y2="110" markerEnd="url(#ngc-d1)" /><g className="ngc-node ngc-soft c-purple ngc-pop ngc-p2"><rect x="230" y="112" width="180" height="56" rx="9" /><text x="320.0" y="137" textAnchor="middle" style={{fontSize:"15px"}}>Structural</text><text className="ngc-sub" x="320.0" y="155" textAnchor="middle">adds or removes elements</text></g><text className="ngc-note" x="320" y="192" textAnchor="middle" style={{fontFamily:"var(--mono)",fontSize:"12px"}}>@if, @for, *ngIf</text><line className="ngc-link ngc-flow c-green" x1="320" y1="62" x2="530" y2="110" markerEnd="url(#ngc-d1)" /><g className="ngc-node ngc-soft c-green ngc-pop ngc-p3"><rect x="440" y="112" width="180" height="56" rx="9" /><text x="530.0" y="137" textAnchor="middle" style={{fontSize:"15px"}}>Attribute</text><text className="ngc-sub" x="530.0" y="155" textAnchor="middle">changes look or behavior</text></g><text className="ngc-note" x="530" y="192" textAnchor="middle" style={{fontFamily:"var(--mono)",fontSize:"12px"}}>ngClass, appHighlight</text>
</svg>
<figcaption>Every component is a directive. Structural and attribute directives add behavior to existing elements.</figcaption>
</figure>

## Structural Directives

A **structural directive** changes the structure of the page. It decides whether an element exists in the DOM and how many copies of it exist.

Structural directives start with an asterisk (`*`). The asterisk is a short form: Angular wraps the element in an `<ng-template>` and lets the directive decide when to render it.

The classic structural directives come from `@angular/common`. Import the ones you use into the component's `imports` list.

### `*ngIf`

`*ngIf` adds the element to the page when its condition is `true` and removes it when the condition is `false`. It can show an alternative template with `else`.

```angular runnable title="*ngIf renders the right message for the login state"
import { Component } from '@angular/core';
import { NgIf } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [NgIf],
  template: `
    <p *ngIf="isLoggedIn; else guest">Welcome back, {{ userName }}.</p>
    <ng-template #guest><p>Please sign in to continue.</p></ng-template>
    <button (click)="isLoggedIn = !isLoggedIn">Toggle login</button>
  `,
})
export class App {
  isLoggedIn = true;
  userName = 'Asha';
}
```

`*ngIf` removes the element from the DOM. It does not hide it with CSS. A removed component is destroyed, and its state is lost.

### `*ngFor`

`*ngFor` renders the element once for each item in a list. It also provides helper values such as `index`, `first` and `last`.

```angular runnable title="*ngFor renders one row for each order"
import { Component } from '@angular/core';
import { NgFor } from '@angular/common';

interface Order {
  id: number;
  total: number;
}

@Component({
  selector: 'app-root',
  imports: [NgFor],
  template: `
    <ul>
      <li *ngFor="let order of orders; let i = index; trackBy: trackById">
        {{ i + 1 }}. Order #{{ order.id }} · ₹{{ order.total }}
      </li>
    </ul>
  `,
})
export class App {
  orders: Order[] = [
    { id: 501, total: 1299 },
    { id: 502, total: 349 },
    { id: 503, total: 2450 },
  ];

  trackById(index: number, order: Order) {
    return order.id;
  }
}
```

`trackBy` tells Angular how to identify each item. When the list changes, Angular reuses the existing rows for items with the same id instead of rebuilding the whole list.

### `*ngSwitch`

`*ngSwitch` picks one template out of several, based on a value. It uses three directives together:

- `[ngSwitch]` on the container holds the value to compare.
- `*ngSwitchCase` marks each possible match.
- `*ngSwitchDefault` marks the template used when nothing matches.

```angular runnable title="*ngSwitch shows a label for each payment status"
import { Component } from '@angular/core';
import { NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [NgSwitch, NgSwitchCase, NgSwitchDefault],
  template: `
    <div [ngSwitch]="status">
      <p *ngSwitchCase="'paid'">✅ Payment received</p>
      <p *ngSwitchCase="'pending'">⏳ Waiting for payment</p>
      <p *ngSwitchCase="'failed'">❌ Payment failed</p>
      <p *ngSwitchDefault>Unknown status</p>
    </div>
  `,
})
export class App {
  status = 'pending';
}
```

Change `status` to `'paid'` or `'failed'` and run it again.

## Modern Control Flow

Since Angular 17, templates have **built-in control flow blocks**: `@if`, `@for` and `@switch`. They do the same jobs as `*ngIf`, `*ngFor` and `*ngSwitch`, with a syntax closer to JavaScript.

Control flow blocks are part of the template language. You do not import anything to use them. New Angular code uses them by default.

### `@if`

`@if` renders its block when the condition is `true`. You can add `@else if` and `@else` blocks.

```angular runnable title="@if chooses the stock message"
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h4>Wireless Mouse</h4>
    @if (stock === 0) {
      <p>Out of stock</p>
    } @else if (stock < 5) {
      <p>Only {{ stock }} left. Order soon.</p>
    } @else {
      <p>In stock</p>
    }
  `,
})
export class App {
  stock = 3;
}
```

### `@for`

`@for` repeats its block for each item in a list. The `track` expression is **required**. It tells Angular how to identify each item, like `trackBy` does for `*ngFor`.

An `@empty` block renders when the list has no items.

```angular runnable title="@for lists tasks and handles an empty list"
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h4>Sprint tasks</h4>
    <ul>
      @for (task of tasks; track task.id; let i = $index) {
        <li>{{ i + 1 }}. {{ task.title }}</li>
      } @empty {
        <li>No tasks in this sprint.</li>
      }
    </ul>
    <button (click)="tasks = []">Clear tasks</button>
  `,
})
export class App {
  tasks = [
    { id: 1, title: 'Design the login page' },
    { id: 2, title: 'Connect the orders API' },
    { id: 3, title: 'Write unit tests' },
  ];
}
```

Click Clear tasks. The `@empty` block replaces the list.

### `@switch`

`@switch` compares one value against several `@case` blocks and renders the first match. `@default` renders when no case matches.

```angular runnable title="@switch shows the permissions for a role"
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <p>Role: {{ role }}</p>
    @switch (role) {
      @case ('admin') { <p>Can create, edit and delete users.</p> }
      @case ('editor') { <p>Can edit content.</p> }
      @default { <p>Read-only access.</p> }
    }
  `,
})
export class App {
  role = 'editor';
}
```

| Task | Classic directive | Control flow block |
|---|---|---|
| Show or hide | `*ngIf="cond; else other"` | `@if (cond) { } @else { }` |
| Repeat | `*ngFor="let x of list; trackBy: fn"` | `@for (x of list; track x.id) { }` |
| Choose one | `[ngSwitch]` + `*ngSwitchCase` | `@switch (value) { @case (...) { } }` |
| Import needed | Yes, from `@angular/common` | No |
| Empty-list template | Write your own `*ngIf` | Built in: `@empty` |
| Tracking items | Optional `trackBy` | Required `track` |

Use `@if`, `@for` and `@switch` in new code. You still need to read `*ngIf`, `*ngFor` and `*ngSwitch`, because most existing Angular projects use them.

## Attribute Directives

An **attribute directive** changes how an existing element looks or behaves. It does not add or remove elements.

### `ngClass`

`ngClass` adds and removes several CSS classes at once. It accepts a string, an array, or an object whose keys are class names and whose values are conditions.

```angular runnable title="ngClass applies classes from conditions"
import { Component } from '@angular/core';
import { NgClass } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [NgClass],
  template: `
    @for (invoice of invoices; track invoice.id) {
      <p [ngClass]="{ overdue: invoice.daysLate > 0, paid: invoice.paid }">
        Invoice {{ invoice.id }}: {{ invoice.paid ? 'Paid' : invoice.daysLate + ' days late' }}
      </p>
    }
  `,
  styles: `
    p { padding: 4px 8px; border-radius: 6px; }
    .overdue { background: #fee2e2; color: #991b1b; font-weight: bold; }
    .paid { background: #dcfce7; color: #166534; }
  `,
})
export class App {
  invoices = [
    { id: 'INV-101', paid: true, daysLate: 0 },
    { id: 'INV-102', paid: false, daysLate: 12 },
  ];
}
```

### `ngStyle`

`ngStyle` sets several inline styles at once from an object. Keys are CSS property names, and you can add units to the key, such as `'width.px'`.

```angular runnable title="ngStyle sets styles from calculated values"
import { Component } from '@angular/core';
import { NgStyle } from '@angular/common';

@Component({
  selector: 'app-root',
  template: `
    <p>Disk usage: {{ used }}%</p>
    <div class="track">
      <div [ngStyle]="{ 'width.%': used, 'background-color': used > 80 ? '#dc2626' : '#2563eb' }" class="bar"></div>
    </div>
  `,
  imports: [NgStyle],
  styles: `
    .track { width: 240px; height: 10px; background: #e5e7eb; border-radius: 5px; overflow: hidden; }
    .bar { height: 100%; }
  `,
})
export class App {
  used = 86;
}
```

| Point | `ngClass` | `[class.x]` binding | `ngStyle` | `[style.x]` binding |
|---|---|---|---|---|
| Changes | Several classes | One class | Several styles | One style |
| Import needed | Yes (`NgClass`) | No | Yes (`NgStyle`) | No |
| Best for | Many conditional classes | One condition | Several calculated values | One calculated value |

## Directive Best Practices

Follow these rules to keep templates fast and easy to read:

- **Prefer control flow blocks.** Use `@if`, `@for` and `@switch` in new code. They need no imports and give better error messages.
- **Always track list items by a stable id.** Use `track item.id`, not `track $index`, when items can be added, removed or reordered.
- **Keep logic out of the template.** Move long conditions into a class property or method with a clear name, such as `canEdit`.
- **Prefer class binding to style binding.** Keep colors and sizes in the stylesheet, and switch classes from the template.
- **Do not hide with `@if` what you only want to hide visually.** `@if` destroys the content. For a quick show/hide that keeps state, bind `[hidden]` or a CSS class.
- **Write a custom directive for repeated behavior.** If several templates need the same DOM behavior, put it in one directive instead of copying code.

Never put two structural directives on the same element, such as `*ngIf` and `*ngFor` together. Angular reports an error. Wrap the element in an `<ng-container>` and put one directive on each, or use `@if` inside `@for`.

## Introduction to Custom Directives

A **custom directive** is your own attribute directive. It packages DOM behavior so you can add it to any element with one attribute.

You create one with the `@Directive` decorator. The selector is usually an attribute in square brackets, such as `'[appHighlight]'`. In a project, the CLI creates it for you:

```bash
ng generate directive highlight
```

Inside the directive:

- `inject(ElementRef)` gives access to the element the directive is attached to.
- `@HostListener('event')` runs a method when that element fires an event.
- `@Input()` lets the template pass settings to the directive.

```angular runnable title="A custom directive highlights rows on hover"
import { Component, Directive, ElementRef, HostListener, Input, inject } from '@angular/core';

@Directive({
  selector: '[appHighlight]',
})
export class Highlight {
  private el = inject(ElementRef);
  @Input() appHighlight = '#fef08a';

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

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

@Component({
  selector: 'app-root',
  imports: [Highlight],
  template: `
    <p appHighlight>Hover me: default yellow</p>
    <p [appHighlight]="'#bfdbfe'">Hover me: custom blue</p>
  `,
})
export class App {}
```

Move the mouse over each paragraph. The same directive applies a different color to each, because the color is passed through the input.

The square brackets in `selector: '[appHighlight]'` mean "match any element that has the attribute `appHighlight`". Without brackets, `'appHighlight'` would match an element named `<appHighlight>`, as a component selector does.

Custom directives are covered in depth in a later lesson.

## Common Mistakes

<div className="ngc-mistakes">

| Mistake | What you see | Fix |
|---|---|---|
| `*ngIf` used without importing `NgIf` | The element always renders, and the console shows `NG0303` | Add `NgIf` to `imports`, or use `@if` |
| `@for` without `track` | A template parse error: `@for loop must have a "track" expression` | Add `track item.id` |
| `*ngIf` and `*ngFor` on one element | An error about multiple template bindings | Use `<ng-container>` for one of them |
| `track $index` on a list that changes order | Rows show the wrong state after sorting | Track a stable id, such as `track item.id` |
| Missing quotes in `*ngSwitchCase="paid"` | The case never matches | Compare to a string: `*ngSwitchCase="'paid'"` |
| Forgetting the brackets in `[ngClass]` | The text `{ overdue: ... }` becomes a class name | Bind it: `[ngClass]="{ ... }"` |

</div>

## Check Yourself

## Exercises

## What to Remember

- Directives add behavior to elements. Structural directives change which elements exist, and attribute directives change how elements look or behave.
- Use `@if`, `@for` (with `track`) and `@switch` in new code, and recognise `*ngIf`, `*ngFor` and `*ngSwitch` in existing code.
- `ngClass` and `ngStyle` set several classes or styles at once. A custom directive packages reusable DOM behavior behind one attribute.

Next: **Pipes**, how to format dates, currency and text directly in the template.
