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
istrue
, Angular instantiates the<div>
inside the DOM. - If
isVisible
isfalse
, 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: `
<button (click)="toggleMessage()">Toggle Message</button>
<div *ngIf="isVisible">
Hello! This message is conditionally displayed.
</div>
`
})
export class NgIfExampleComponent {
isVisible = true;
toggleMessage() {
this.isVisible = !this.isVisible;
}
}
In this example:
- Clicking the button toggles the value of
isVisible
. - When
isVisible
istrue
, the<div>
is rendered. - When
isVisible
isfalse
, 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 isfalse
.
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: `
<ul>
<li *ngFor="let fruit of fruits">
{{ fruit }}
</li>
</ul>
`
})
export class NgForExampleComponent {
fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
}
In this example:
*ngFor
iterates over thefruits
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 iffruits
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
isfalse
. - Internally,
ViewContainerRef
is used to add or remove the element from the DOM. TemplateRef
references the template of the element.
Performance Considerations
- Avoid Complex Conditions in
*ngIf
: Use component properties to store complex logic instead of evaluating it directly in templates. - 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
}
- Lazy Loading with
*ngIf
: Use*ngIf
to prevent rendering heavy components until needed.
Best Practices
- Prefer
*ngIf
over CSSdisplay: none
when you need elements removed from the DOM. - Use
*ngFor
withtrackBy
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: `
<h2>User List</h2>
<ul *ngIf="users.length; else noUsers">
<li *ngFor="let user of users; let i = index">
{{ i + 1 }}. {{ user.name }} - {{ user.email }}
</li>
</ul>
<ng-template #noUsers>
<p>No users found.</p>
</ng-template>
`
})
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.
Leave a Reply