Angular Directive Overview: Structural Directives

Angular Directive Overview: Structural Directives

Add, remove, or swap elements in the DOM with reusable directives that can be added to any host element

This is part of a 3-part overview of Angular directives. Also see:

What are Structural Directives?

Structural directives are an essential feature in Angular that let you change the HTML structure by adding, removing, or swapping elements in the DOM (Document Object Model). These directives allow you to conditionally include or loop through elements, enabling you to dynamically alter your application's UI depending on your data's status or certain conditions.

Here's a closer look at how structural directives work and some common examples:

How Structural Directives Work

Unlike attribute directives, which only change the appearance or behavior of an element, structural directives actually add, remove, or manipulate elements in the DOM. The ability to manipulate the DOM is powerful because it lets you create highly dynamic layouts.

In Angular templates, structural directives are typically recognized by a leading asterisk (*) syntax, such as *ngIf or *ngFor. The asterisk is a syntactic sugar that makes it easier to write and read structural directives within templates.

Common Structural Directives:

  1. *ngIf:

    • Purpose: Used to conditionally include an HTML element based on the truthiness of the expression provided to it.

    • Usage: You might use *ngIf to show or hide an element based on a condition. For example, displaying a message only if a user is logged in.

    <!-- Allows us to conditionally render content-->
    <div *ngIf="loggedIn">
        Welcome back, {{ name }}
    </div>
  1. *ngFor:

    • Purpose: A directive that is used to repeat a template for each item in a list.

    • Usage: Commonly used for generating lists or tables in an HTML template from an array of data. It provides local variables such as index, first, last, even, and odd for additional control within the loop.

    <ul>
      <li *ngFor="let user of users">{{ user.name }}</li>
    </ul>
  1. *ngSwitch:
  • Purpose: A directive that switches among multiple possible views based on a matching case.

  • Usage: Similar to the switch statement in many programming languages, *ngSwitch is useful for displaying different views based on the value of a variable. It works in tandem with *ngSwitchCase and *ngSwitchDefault.

      <div [ngSwitch]="fruit">
        <div *ngSwitchCase="'apple'">Apple is selected</div>
        <div *ngSwitchCase="'banana'">Banana is selected</div>
        <div *ngSwitchCase="'orange'">Orange is selected</div>
        <div *ngSwitchDefault>Unknown fruit selected</div>
      </div>
    

Behind the Scenes

  • TemplateRef and ViewContainerRef: Structural directives work by using TemplateRef to access and manipulate templates, and ViewContainerRef to manage the container where these templates are embedded. This provides the directives the ability to control where and how the templated elements are displayed in the DOM.

  • Shorthand syntax: Angular provides a special shorthand syntax for structural directives, allowing you to define inputs and local variables within the directive in a concise manner. This syntax is parsed and expanded into a more verbose form by Angular. Angular transforms the asterisk in front of a structural directive into an <ng-template> that surrounds the host element and its descendants.

Note: Only one structural directive can be applied per element

Custom Structural Directives

Beyond the built-in structural directives, Angular allows you to create custom structural directives. This can be particularly useful for hiding complex DOM manipulation behind simple syntax, making your templates cleaner and more declarative.

Example of a Custom Structural Directive

Let's create a directive named AppHasPermissionDirective that will check if the current user has the specified permission and render the content accordingly. For simplicity, we'll assume there's a AuthService that provides the current user's permissions.

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
import { AuthService } from './auth.service';

@Directive({
  selector: '[appHasPermission]'
})
export class AppHasPermissionDirective {
  private hasView = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private authService: AuthService
  ) {}

  @Input() set appHasPermission(permission: string) {
    const userHasPermission = this.authService.hasPermission(permission);

    if (userHasPermission && !this.hasView) {
      // If the user has permission and the view hasn't been created yet, create it.
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (!userHasPermission && this.hasView) {
      // If the user does not have permission and the view exists, clear it.
      this.viewContainer.clear();
      this.hasView = false;
    }
  }
}

Now we can use the directive that we've just defined in our template to conditionally render content to the user if they have specific permissions.

<div *appHasPermission="'admin'">
  This content is only visible to users with the 'admin' permission.
</div>