Angular developers have used *ngIf and *ngFor for years. They work — but they also hide complexity, encourage messy templates, and become painful as UI logic grows.
Angular’s template control flow syntax (@if, @for, @empty, @switch) solves these problems with clear, explicit, and readable blocks.
If you’re still writing Angular templates the old way, this article shows why you should stop — and what to use instead.
**What’s Wrong with ngIf and ngFor?
The issue isn’t that they’re broken — it’s that they don’t scale well.
Common problems:
- Hidden logic
- Asterisk (*) magic that’s hard to reason about
- Nested conditions become unreadable
- Empty states require extra boilerplate
<div *ngIf="user; else loading">
<app-profile [user]="user"></app-profile>
</div>
<ng-template #loading>
<app-spinner></app-spinner>
</ng-template>
This works — but the logic is split across multiple places.
@if — Clear, Explicit Conditional Rendering
✅ Cleaner Alternative
@if (user) {
<app-profile [user]="user"></app-profile>
} @else {
<app-spinner></app-spinner>
}
Why this is better:
- No
- No indirection
- Reads like real control flow
- Easy to extend and refactor
Nested conditions also stay readable:
@if (user) {
@if (user.isAdmin) {
<admin-panel />
} @else {
<user-dashboard />
}
}
*@for — A Better ngFor
❌ Old Way
<li *ngFor="let item of items; trackBy: trackById">
{{ item.name }}
</li>
✅ New Way
@for (item of items; track item.id) {
<li>{{ item.name }}</li>
}
Built-in variables are explicit and intuitive:
@for (item of items; let i = $index) {
<p>{{ i + 1 }}. {{ item.name }}</p>
}
Available variables:
- $index
- $count
- $first
- $last
- $even
- $odd
@empty — No More Manual Empty State Checks
This is where Angular really shines.
❌ Old Pattern
<ul *ngIf="items.length > 0; else empty">
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<ng-template #empty>
No items found
</ng-template>
✅ New Pattern
@for (item of items) {
<li>{{ item }}</li>
} @empty {
<li>No items found</li>
}
Why @empty is a big win:
- No extra conditions
- Empty state stays next to the list
- Perfect for tables, dropdowns, dashboards
@switch — Readable State-Based UI
❌ Old ngSwitch
<div [ngSwitch]="status">
<p *ngSwitchCase="'loading'">Loading...</p>
<p *ngSwitchCase="'success'">Success</p>
<p *ngSwitchDefault>Error</p>
</div>
✅ New Control Flow
@switch (status) {
@case ('loading') {
<p>Loading...</p>
}
@case ('success') {
<p>Success</p>
}
@default {
<p>Error</p>
}
}
This reads exactly how it behaves.
Works Perfectly with Signals
items = signal<string[]>([]);
@for (item of items()) {
<p>{{ item }}</p>
} @empty {
<p>No data available</p>
}
No subscriptions.
No async pipe noise.
Angular handles reactivity automatically.
Performance & Maintainability Benefits
Angular’s control flow blocks:
- Reduce unnecessary DOM nodes
- Improve compile-time optimizations
- Work seamlessly with zoneless Angular
- Make templates easier to test and review This is not just syntax sugar — it’s better architecture.
*Should You Stop Using ngIf Completely?
No — existing code is fine.
But for:
- New components
- New features
- Signal-based state
- Complex UI logic
👉 @if, @for, and @empty should be your default choice
Final Thoughts
*ngIf and *ngFor were great — but Angular templates have moved on.
If you care about:
- clean templates
- readable UI logic
- scalable Angular apps
then it’s time to stop using *ngIf like it’s 2018 and start writing templates the modern Angular way.
Top comments (0)