Directives are one of the most powerful and frequently used features in Angular. They allow you to change layout, style, behavior, and structure directly from your templates.

This guide covers four essential Angular building blocks:

Designed for beginners learning Angular as well as developers working on real-world systems.

1. What Are Angular Directives?

A directive is a class that adds behavior to elements in the template.

Three broad categories:

  1. Component Directives
    Comes with a template.

  2. Structural Directives
    Change the DOM layout.
    Example: *ngIf, *ngFor, *ngSwitchCase

  3. Attribute Directives
    Change the appearance or behavior of an element.
    Example: ngClass, ngStyle

We’ll focus on the commonly used attribute and structural directives.

2. ngClass — Apply Classes Dynamically

Use ngClass when you want to apply CSS classes conditionally.

Example 1: Simple Binding

<div [ngClass]="'highlight'">Hello</div>

Example 2: Conditional Classes

<div [ngClass]="{ 'active': isActive, 'disabled': !isActive }">
  User Status
</div>

Example 3: Multiple Classes

<div [ngClass]="['box', theme]"></div>

Real-World Case: Status Badge

<span [ngClass]="{
  'success': order.status === 'Delivered',
  'warning': order.status === 'Pending',
  'error': order.status === 'Cancelled'
}">
  {{ order.status }}
</span>

3. ngStyle — Apply Inline Styles Conditionally

ngStyle helps you apply inline CSS dynamically.

Example 1: Single Style

<div [ngStyle]="{ 'color': 'red' }">Alert</div>

Example 2: Dynamic Value

<div [ngStyle]="{ 'font-size': size + 'px' }"></div>

Example 3: Conditional Styling

<div [ngStyle]="{
  'background-color': value > 0 ? 'lightgreen' : 'salmon'
}">
  {{ value }}
</div>

Real-World Case: Display Stock Status

<span [ngStyle]="{
  'color': stock > 10 ? 'green' : 'orange'
}">
  {{ stock }} items available
</span>

4. ngSwitch — Conditional Rendering for Multiple Conditions

Use ngSwitch when you have multiple possible views based on a single value.

Basic Example

<div [ngSwitch]="role">
  <p *ngSwitchCase="'admin'">Admin Panel</p>
  <p *ngSwitchCase="'user'">User Dashboard</p>
  <p *ngSwitchDefault>Unknown Role</p>
</div>

Real-World Case: Payment Status

<div [ngSwitch]="payment.status">
  <span *ngSwitchCase="'paid'">Payment Successful</span>
  <span *ngSwitchCase="'failed'">Payment Failed</span>
  <span *ngSwitchCase="'pending'">Payment Pending</span>
  <span *ngSwitchDefault>Status Not Available</span>
</div>

This is cleaner and more readable than multiple *ngIf blocks.

5. Async Pipe — One of Angular’s Most Useful Tools

The Async Pipe is used to subscribe to Observables or Promises directly from the template.

Without Async Pipe, you must manually:

Async Pipe handles all of this automatically.

Why Use Async Pipe?

Benefits

  1. No memory leaks (auto unsubscribe)

  2. Cleaner code

  3. No manual subscription logic in TypeScript

  4. Works perfectly with HttpClient, RxJS streams, WebSockets

Example 1: Observables with Async Pipe

Component:

data$ = this.http.get('/api/products');

Template:

<div *ngIf="data$ | async as products">
  {{ products | json }}
</div>

No need to subscribe manually.

Example 2: Loading User Profile

user$ = this.userService.getUser();
<div *ngIf="user$ | async as user">
  <p>{{ user.name }}</p>
  <p>{{ user.email }}</p>
</div>

Example 3: Using Async Pipe Inside ngFor

orders$ = this.orderService.getOrders();
<div *ngFor="let order of orders$ | async">
  {{ order.id }} - {{ order.amount }}
</div>

6. Async Pipe with Multiple Streams

Angular can combine multiple Observables using combineLatest or forkJoin.

result$ = combineLatest([
  this.userService.getUser(),
  this.orderService.getOrders()
]);

Template

<div *ngIf="result$ | async as data">
  {{ data[0].name }}
  {{ data[1].length }} orders
</div>

7. Combining Directives and Async Pipe (Real Case Study)

Scenario: You are building an order dashboard.

<div *ngIf="orders$ | async as orders">
  <div *ngFor="let order of orders" [ngClass]="{
       'delivered': order.status === 'Delivered',
       'pending': order.status === 'Pending'
     }">

    <p>{{ order.amount | currency:'INR' }}</p>

    <span [ngStyle]="{
      'color': order.status === 'Delivered' ? 'green' : 'orange'
    }">
      {{ order.status }}
    </span>

    <div [ngSwitch]="order.paymentMode">
      <span *ngSwitchCase="'card'">Card Payment</span>
      <span *ngSwitchCase="'cash'">Cash on Delivery</span>
      <span *ngSwitchDefault>Other</span>
    </div>

  </div>
</div>

This combines everything:

Using these together results in clean, scalable, maintainable Angular templates.

8. Best Practices for Directives and Async Pipe

ngClass / ngStyle

ngSwitch

Async Pipe

Conclusion

Angular Directives and the Async Pipe are essential building blocks for writing clean and scalable user interfaces.
They allow you to:

When combined together, they provide a powerful and elegant way to handle complex UI scenarios without writing extra TypeScript code.