Introduction to Structural Directives in Angular

In Angular, structural directives are a special type of directive that alters the DOM structure by adding or removing elements dynamically. Unlike attribute directives that modify the appearance or behavior of an existing element, structural directives change the layout of the view by adding, removing, or manipulating elements based on conditions or data. This makes them extremely useful for dynamic UI rendering in Angular applications.

The most commonly used structural directives in Angular are:

  • *ngIf: Conditionally includes or excludes an element in the DOM.
  • *ngFor: Iterates over collections like arrays or lists and renders elements for each item.

This post will provide a deep dive into structural directives, their working mechanisms, practical examples, and best practices for using them effectively.

Understanding Structural Directives

Structural directives use the asterisk (*) syntax in Angular templates, such as *ngIf and *ngFor. The asterisk is syntactic sugar for an underlying template transformation. Angular internally wraps the element with an <ng-template> and manages its insertion or removal based on the directive’s logic.

How Structural Directives Work

When you write:

<div *ngIf="isVisible">Hello World</div>

Angular internally transforms it into:

<ng-template [ngIf]="isVisible">
  <div>Hello World</div>
</ng-template>

Here’s what happens:

  • If isVisible is true, Angular instantiates the <div> inside the DOM.
  • If isVisible is false, Angular removes the <div> from the DOM entirely.
  • This behavior is different from hiding an element with CSS, as the element does not exist in the DOM when the condition is false.

The *ngIf Directive

The *ngIf directive is used to conditionally render an element. It is ideal for situations where you want elements to appear or disappear based on application logic.

Basic Syntax

<div *ngIf="condition">
  Content is visible if condition is true
</div>

Example: Displaying a Message Conditionally

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

@Component({
  selector: 'app-ngif-example',
  template: `
&lt;button (click)="toggleMessage()"&gt;Toggle Message&lt;/button&gt;
&lt;div *ngIf="isVisible"&gt;
  Hello! This message is conditionally displayed.
&lt;/div&gt;
` }) export class NgIfExampleComponent { isVisible = true; toggleMessage() {
this.isVisible = !this.isVisible;
} }

In this example:

  • Clicking the button toggles the value of isVisible.
  • When isVisible is true, the <div> is rendered.
  • When isVisible is false, the <div> is removed from the DOM.

Using else with *ngIf

Angular provides the else clause to display an alternative template when the condition is false.

<div *ngIf="isVisible; else noContent">
  Content is visible
</div>

<ng-template #noContent>
  Content is hidden
</ng-template>

Here:

  • #noContent is a template reference.
  • Angular renders the <ng-template> content when the condition is false.

Using then and else Together

<div *ngIf="isVisible; then visibleTemplate; else hiddenTemplate"></div>

<ng-template #visibleTemplate>
  <p>The content is visible!</p>
</ng-template>

<ng-template #hiddenTemplate>
  <p>The content is hidden!</p>
</ng-template>

This syntax gives you more control over which template is rendered for true or false conditions.


The *ngFor Directive

The *ngFor directive allows you to loop over arrays or collections and render elements dynamically for each item.

Basic Syntax

<li *ngFor="let item of items">
  {{ item }}
</li>

Example: Displaying a List of Items

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

@Component({
  selector: 'app-ngfor-example',
  template: `
&lt;ul&gt;
  &lt;li *ngFor="let fruit of fruits"&gt;
    {{ fruit }}
  &lt;/li&gt;
&lt;/ul&gt;
` }) export class NgForExampleComponent { fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']; }

In this example:

  • *ngFor iterates over the fruits array.
  • A <li> element is created for each item in the array.
  • Angular automatically updates the DOM when the array changes.

Using index with *ngFor

You can access the index of the current item using let i = index.

<li *ngFor="let fruit of fruits; let i = index">
  {{ i + 1 }}. {{ fruit }}
</li>

Using first, last, even, odd

Angular also provides context variables:

  • first: true if the item is the first in the list.
  • last: true if the item is the last in the list.
  • even: true if the item index is even.
  • odd: true if the item index is odd.

Example:

<li *ngFor="let fruit of fruits; let i = index; let isEven = even">
  {{ i + 1 }}. {{ fruit }} <span *ngIf="isEven">(Even)</span>
</li>

Combining *ngIf and *ngFor

You can combine *ngIf and *ngFor to display lists conditionally.

Example: Display Items Only if the Array Has Elements

<ul *ngIf="fruits.length > 0; else noFruits">
  <li *ngFor="let fruit of fruits">{{ fruit }}</li>
</ul>

<ng-template #noFruits>
  <p>No fruits available.</p>
</ng-template>

Here:

  • The <ul> is displayed only if fruits array is not empty.
  • Otherwise, the <ng-template> shows a message.

Custom Structural Directives

In addition to built-in directives like *ngIf and *ngFor, you can create custom structural directives to manipulate the DOM based on your specific needs.

Example: A Custom *appUnless Directive

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

@Directive({
  selector: '[appUnless]'
})
export class UnlessDirective {
  constructor(private templateRef: TemplateRef<any>, private vcRef: ViewContainerRef) {}

  @Input() set appUnless(condition: boolean) {
if (!condition) {
  this.vcRef.createEmbeddedView(this.templateRef);
} else {
  this.vcRef.clear();
}
} }

Usage:

<p *appUnless="isLoggedIn">You must log in to see this message.</p>

Explanation:

  • The element is rendered only if isLoggedIn is false.
  • Internally, ViewContainerRef is used to add or remove the element from the DOM.
  • TemplateRef references the template of the element.

Performance Considerations

  1. Avoid Complex Conditions in *ngIf: Use component properties to store complex logic instead of evaluating it directly in templates.
  2. Use TrackBy with *ngFor: Improves performance when rendering large lists by avoiding unnecessary DOM manipulation.

Example:

<li *ngFor="let fruit of fruits; trackBy: trackByFn">{{ fruit }}</li>
trackByFn(index: number, item: string) {
  return item; // unique identifier
}
  1. Lazy Loading with *ngIf: Use *ngIf to prevent rendering heavy components until needed.

Best Practices

  • Prefer *ngIf over CSS display: none when you need elements removed from the DOM.
  • Use *ngFor with trackBy for optimal performance with dynamic lists.
  • Keep structural directives readable by avoiding deeply nested *ngIf or *ngFor.
  • Consider combining *ngIf and *ngFor efficiently to reduce unnecessary template logic.
  • For reusable conditional logic, consider creating custom structural directives.

Real-World Examples

Example 1: Displaying a List of Users Conditionally

@Component({
  selector: 'app-user-list',
  template: `
&lt;h2&gt;User List&lt;/h2&gt;
&lt;ul *ngIf="users.length; else noUsers"&gt;
  &lt;li *ngFor="let user of users; let i = index"&gt;
    {{ i + 1 }}. {{ user.name }} - {{ user.email }}
  &lt;/li&gt;
&lt;/ul&gt;
&lt;ng-template #noUsers&gt;
  &lt;p&gt;No users found.&lt;/p&gt;
&lt;/ng-template&gt;
` }) export class UserListComponent { users = [
{ name: 'Alice', email: '[email protected]' },
{ name: 'Bob', email: '[email protected]' }
]; }

Example 2: Display Components Conditionally

<app-login *ngIf="!isLoggedIn"></app-login>
<app-dashboard *ngIf="isLoggedIn"></app-dashboard>

This approach dynamically shows either the login form or the dashboard based on the authentication state.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *