Introduction to Angular Directives
Directives in Angular are classes that add additional behavior to elements in your Angular applications. They are one of the most powerful features in Angular, allowing you to manipulate the DOM, apply conditional rendering, create repeating elements, and more. Angular has three categories of directives:
- Component Directives – Directives with templates (essentially Angular components)
- Structural Directives – Change the DOM layout by adding/removing DOM elements (
*ngIf
,*ngFor
,*ngSwitch
) - Attribute Directives – Change appearance or behavior of an element (
ngClass
,ngStyle
)
This cheat sheet focuses on the most commonly used structural directives: *ngIf
and *ngFor
.
Understanding Structural Directives
What Makes Them “Structural”
- Identified by the asterisk (*) prefix
- Fundamentally change DOM structure (add/remove elements)
- Under the hood, transform into
<ng-template>
elements - Can only use one structural directive per host element
The Asterisk (*) Syntax
The asterisk (*) is shorthand syntax. Angular translates:
<!-- Shorthand syntax -->
<div *ngIf="condition">Content</div>
<!-- Into this expanded syntax -->
<ng-template [ngIf]="condition">
<div>Content</div>
</ng-template>
The *ngIf Directive
*ngIf
conditionally includes or removes an element from the DOM based on a boolean expression.
Basic Usage
<!-- Only shows if isVisible is truthy -->
<div *ngIf="isVisible">This text will be shown if isVisible is true</div>
<!-- With component properties -->
<div *ngIf="user">Welcome, {{ user.name }}!</div>
<!-- With expressions -->
<div *ngIf="items.length > 0">You have items in your cart</div>
Using Else Statements
<!-- Basic else example -->
<div *ngIf="isLoggedIn; else loggedOut">
Welcome back, {{ username }}!
</div>
<ng-template #loggedOut>
Please log in to continue.
</ng-template>
<!-- With multiple blocks -->
<div *ngIf="status === 'success'; then successBlock else errorBlock"></div>
<ng-template #successBlock>
<div class="alert alert-success">Operation completed successfully!</div>
</ng-template>
<ng-template #errorBlock>
<div class="alert alert-danger">An error occurred!</div>
</ng-template>
Using As Syntax (Storing the Result)
<!-- Store evaluated condition result in a variable -->
<div *ngIf="userResponse as user; else loading">
{{ user.name }} has been successfully loaded.
</div>
<ng-template #loading>
Loading user data...
</ng-template>
<!-- Particularly useful with async pipe -->
<div *ngIf="userObservable | async as user; else loading">
{{ user.name }}
</div>
<ng-template #loading>
<spinner></spinner>
</ng-template>
Common Patterns
<!-- Loading states -->
<div *ngIf="isLoading">Loading...</div>
<div *ngIf="!isLoading && hasData">Data loaded successfully!</div>
<div *ngIf="!isLoading && !hasData">No data available.</div>
<!-- Role-based content -->
<div *ngIf="user?.role === 'admin'">Admin Panel</div>
<!-- Hiding until data is ready -->
<div *ngIf="isDataReady">{{ processedData }}</div>
Performance Considerations
<!-- Hidden content is removed from DOM (better for large content) -->
<div *ngIf="showDetails">
<!-- Large content or expensive components -->
<app-detailed-view [data]="detailedData"></app-detailed-view>
</div>
<!-- Alternative: [hidden] only changes CSS visibility, content remains in DOM -->
<div [hidden]="!showDetails">
<!-- Use [hidden] for content that toggles frequently -->
<app-detailed-view [data]="detailedData"></app-detailed-view>
</div>
The *ngFor Directive
*ngFor
repeats a template for each item in an iterable (like arrays or collections).
Basic Usage
<!-- Basic iteration -->
<ul>
<li *ngFor="let item of items">{{ item.name }}</li>
</ul>
<!-- With index -->
<ul>
<li *ngFor="let item of items; let i = index">
{{ i + 1 }}. {{ item.name }}
</li>
</ul>
Local Variables
*ngFor
provides several variables you can use within the template:
Variable | Description |
---|---|
index | Zero-based index of the current item |
first | Boolean – true if the current item is the first item |
last | Boolean – true if the current item is the last item |
even | Boolean – true if the current index is even |
odd | Boolean – true if the current index is odd |
<!-- Using multiple local variables -->
<div *ngFor="let item of items;
let i = index;
let isFirst = first;
let isLast = last;
let isEven = even;
let isOdd = odd">
<span *ngIf="isFirst">First item: </span>
<span *ngIf="isLast">Last item: </span>
<span *ngIf="isEven">Even row: </span>
<span *ngIf="isOdd">Odd row: </span>
<span>{{ i }}: {{ item.name }}</span>
</div>
TrackBy Function
Using trackBy
improves performance by helping Angular identify which items have changed.
<!-- Using trackBy -->
<ul>
<li *ngFor="let item of items; trackBy: trackByFn">{{ item.name }}</li>
</ul>
// In component class
trackByFn(index: number, item: any): number {
return item.id; // unique identifier
}
Nested *ngFor
<!-- Nested loops -->
<div *ngFor="let group of groups">
<h3>{{ group.name }}</h3>
<ul>
<li *ngFor="let item of group.items">
{{ item.name }}
</li>
</ul>
</div>
Working with Object Key-Value Pairs
<!-- With keyvalue pipe for objects -->
<div *ngFor="let entry of userObject | keyvalue">
<strong>{{ entry.key }}:</strong> {{ entry.value }}
</div>
Common Patterns
<!-- Empty state handling -->
<div *ngIf="items.length === 0">
No items found.
</div>
<ul *ngIf="items.length > 0">
<li *ngFor="let item of items">{{ item.name }}</li>
</ul>
<!-- Filtering inside template -->
<div *ngFor="let item of items">
<div *ngIf="item.isActive">
{{ item.name }} (Active)
</div>
</div>
<!-- With array indexes -->
<div *ngFor="let item of items; let i = index">
<button (click)="removeItem(i)">Remove</button>
{{ item.name }}
</div>
Combined *ngIf and *ngFor Patterns
Problem: Cannot use both on same element
<!-- THIS WON'T WORK! -->
<div *ngIf="items.length > 0" *ngFor="let item of items">
{{ item.name }}
</div>
Solution 1: Nest the directives
<!-- Use ngIf on container, ngFor on inner elements -->
<div *ngIf="items && items.length > 0">
<div *ngFor="let item of items">
{{ item.name }}
</div>
</div>
Solution 2: Use ng-container
<ng-container>
is a logical container that doesn’t render any HTML element.
<!-- Using ng-container to group multiple structural directives -->
<ng-container *ngIf="items && items.length > 0">
<div *ngFor="let item of items">
{{ item.name }}
</div>
</ng-container>
<!-- Alternative: ngFor outside with conditional inner content -->
<ng-container *ngFor="let item of items">
<div *ngIf="item.isVisible">
{{ item.name }}
</div>
</ng-container>
Advanced Techniques
Conditional Group of Elements with ng-container
<!-- Group multiple elements under one condition -->
<ng-container *ngIf="isLoggedIn">
<h2>Welcome back!</h2>
<p>You have {{ notifications }} new notifications</p>
<button (click)="logout()">Logout</button>
</ng-container>
Dynamic Templates with ngTemplateOutlet
<!-- Define reusable templates -->
<ng-template #itemTemplate let-item let-i="index">
<div class="item">
{{ i }}: {{ item.name }}
</div>
</ng-template>
<!-- Use in ngFor -->
<ng-container *ngFor="let item of items; let i = index">
<ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item, index: i}">
</ng-container>
</ng-container>
Using *ngIf with Observables and Async Pipe
<!-- Handling async data elegantly -->
<ng-container *ngIf="users$ | async as users">
<div *ngIf="users.length > 0; else noUsers">
<div *ngFor="let user of users">
{{ user.name }}
</div>
</div>
<ng-template #noUsers>
<p>No users found</p>
</ng-template>
</ng-container>
Using NgSwitch with NgFor
<div [ngSwitch]="items.length">
<p *ngSwitchCase="0">No items</p>
<div *ngSwitchDefault>
<div *ngFor="let item of items">
{{ item.name }}
</div>
</div>
</div>
Common Challenges and Solutions
Challenge: Accessing Current Element in *ngFor
<!-- Using template reference variables -->
<div *ngFor="let item of items" #itemElement>
<button (click)="processElement(itemElement, item)">Process</button>
</div>
Challenge: Animations with *ngIf or *ngFor
// In component
import { trigger, transition, style, animate } from '@angular/animations';
@Component({
// ...
animations: [
trigger('fadeIn', [
transition(':enter', [
style({ opacity: 0 }),
animate('300ms', style({ opacity: 1 }))
]),
transition(':leave', [
animate('300ms', style({ opacity: 0 }))
])
])
]
})
<!-- In template -->
<div *ngIf="isVisible" [@fadeIn]>Animated content</div>
<div *ngFor="let item of items" [@fadeIn]>
{{ item.name }}
</div>
Challenge: Conditional CSS Classes
<!-- Using ngClass with ngFor -->
<div *ngFor="let item of items; let i = index"
[ngClass]="{'active': selectedIndex === i,
'even': i % 2 === 0,
'odd': i % 2 !== 0}">
{{ item.name }}
</div>
Challenge: Optimizing Long Lists
<!-- Virtual scrolling (requires @angular/cdk) -->
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items" class="item">
{{ item.name }}
</div>
</cdk-virtual-scroll-viewport>
The *ngSwitch Directive Family
While not as common as *ngIf
and *ngFor
, *ngSwitch
is useful for multiple conditional cases.
<div [ngSwitch]="status">
<div *ngSwitchCase="'loading'">Loading data...</div>
<div *ngSwitchCase="'success'">Data loaded successfully!</div>
<div *ngSwitchCase="'error'">Failed to load data</div>
<div *ngSwitchDefault>Unknown status</div>
</div>
Custom Structural Directives
You can create your own structural directives for reusable conditional logic.
// Example: unless directive (opposite of ngIf)
@Directive({
selector: '[appUnless]'
})
export class UnlessDirective {
@Input() set appUnless(condition: boolean) {
if (!condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
}
<!-- Usage -->
<div *appUnless="isHidden">
This content shows when isHidden is false
</div>
Performance Best Practices
For *ngIf
- *Prefer ngIf over hidden for large content blocks that aren’t frequently toggled
- Use else templates rather than duplicate *ngIf conditions
- Combine with async pipe for clean handling of observables
For *ngFor
- Always use trackBy for lists that can change
- Apply filtering in component rather than filtering within *ngFor loops
- Use virtual scrolling for very long lists (hundreds of items)
- Optimize template size – keep content inside *ngFor light
Debugging Techniques
Understanding Error Messages
Error Message | Likely Cause | Solution |
---|---|---|
“Can’t have multiple template bindings on one element” | Using multiple structural directives | Use ng-container to nest directives |
“Cannot read property ‘length’ of undefined” | Accessing property before data is loaded | Use safe navigation operator or *ngIf guard |
“Error trying to diff ‘[object Object]'” | Object without proper identity | Implement trackBy function |
Debug *ngFor Variables
<!-- Debugging by displaying context variables -->
<div *ngFor="let item of items; let i = index; let c = count">
{{ i }} of {{ c }}: {{ item | json }}
</div>
Resources for Further Learning
Official Documentation
Tools
- Angular DevTools – Chrome extension for debugging
- Augury – Chrome extension for Angular inspection
Community Resources
Advanced Topics