Angular Conditional Rendering
Conditional rendering shows or hides parts of the template based on state.
Conditional Rendering Essentials
- Use
@if
/@else if
/@else
for conditional logic. - Use
@switch
to select one view among many. - Signals: Drive conditions from signals and read them with
sig()
in templates. - Hide vs remove:
@if
removes from the DOM; use[hidden]
or CSS to hide without destroying. - Legacy:
*ngIf
and[ngSwitch]
remain supported but are not shown here.
@if (condition) { <p>Shown</p> } @else { <p>Hidden</p> }
@switch (value) {
@case ('x') { <p>X</p> }
@default { <p>Other</p> }
}
<div [hidden]="!isVisible">Hidden but in DOM</div>
Note: See Control Flow for @if
/@switch
/@for
, Templates for markup/interpolation, Events for handling input, and Lists for rendering collections.
Basic Conditional Rendering
- Render content with
@if
/@else
. - Drive booleans from signals; keep template expressions simple.
@if (show()) { <p>Now you see me!</p> } @else { <p>Now I'm hidden.</p> }
Example
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<h3>Conditional Rendering</h3>
<button (click)="show.set(!show())">Toggle</button>
@if (show()) { <p>Now you see me!</p> } @else { <p>Now I'm hidden.</p> }
`
})
export class App {
show = signal(true);
}
bootstrapApplication(App);
<app-root></app-root>
Example explained
- @if (show()): Reads the
show
signal; renders the first block when true, otherwise the@else
block. - Toggle: The button calls
show.set(!show())
to flip the signal value. - Signals in templates: Read signals by calling them (e.g.,
show()
).
Notes:
- Keep expressions light: Avoid calling methods in templates; update signals in handlers/services.
- Legacy:
*ngIf
withthen/else
remains supported. - Be explicit: Use clear conditions (e.g.,
isVisible
) over complex truthy/falsy checks.
@switch
- Switch on a single value with
@switch
and render the matching case. - Always provide an explicit default for unexpected values with
@default
.
@switch (status) {
@case ('loading') { <p>Loading...</p> }
@case ('success') { <p>Success!</p> }
@case ('error') { <p style="color:crimson">Error!</p> }
@default { <p>Unknown status</p> }
}
Example
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<h3>Conditional Rendering with @switch</h3>
<label>
Status:
<select (change)="status.set($any($event.target).value)">
<option value="loading">loading</option>
<option value="success">success</option>
<option value="error">error</option>
</select>
</label>
@switch (status()) {
@case ('loading') { <p>Loading...</p> }
@case ('success') { <p>Success!</p> }
@case ('error') { <p style="color:crimson">Error!</p> }
@default { <p>Unknown status</p> }
}
`
})
export class App {
status = signal<'loading' | 'success' | 'error' | string>('loading');
}
bootstrapApplication(App);
<app-root></app-root>
Example explained
- @switch (status()): Chooses one case to render based on the current
status
signal value. - @case / @default: Renders the matching
@case
; falls back to@default
when no case matches. - Change handler: The
select
setsstatus
viastatus.set($any($event.target).value)
.
Notes:
- Default matters: Always provide an
@default
case for unexpected values. - Stable values: Switch on simple primitives (e.g., strings) rather than objects for predictable matching.
Multi-state with @if
- Use
@if
/@else if
/@else
for readable multi-state flows. - Drive state transitions from the component (e.g., timers, flags) via signals.
@if (!loading() && !error()) {
<p>Content loaded successfully.</p>
} @else if (loading()) {
<p>Loading...</p>
} @else {
<p style="color:crimson">Something went wrong.</p>
}
Example
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
styles: [`
.toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
`],
template: `
<h3>Multi-state with @if</h3>
<div class="toolbar">
<button (click)="startLoading()">Start Loading</button>
<button (click)="showError()">Set Error</button>
<button (click)="reset()">Reset</button>
<span style="margin-left:8px;color:#666">loading={{ loading() }} error={{ error() }}</span>
</div>
@if (!loading() && !error()) {
<p>Content loaded successfully.</p>
} @else if (loading()) {
<p>Loading...</p>
} @else {
<p style="color:crimson">Something went wrong.</p>
}
`
})
export class App {
loading = signal(false);
error = signal(false);
private _timer: any;
startLoading() {
this.loading.set(true);
this.error.set(false);
clearTimeout(this._timer);
this._timer = setTimeout(() => {
this.loading.set(false);
}, 800);
}
showError() {
this.error.set(true);
this.loading.set(false);
}
reset() {
this.loading.set(false);
this.error.set(false);
}
}
bootstrapApplication(App);
<app-root></app-root>
Example explained
- Multi-branch @if: Renders success, loading, or error sections based on the
loading()
anderror()
signals. - startLoading(): Sets
loading
to true, clears error, then turns loading off after 800ms (simulating async work). - showError() / reset():
showError()
setserror
and clearsloading
;reset()
clears both.
Notes:
- Readable flows: Prefer
@else if
chains for multi-state UIs; use named templates only when you need to reference blocks dynamically. - Avoid flicker: Drive state from the component; clear timers and async work to prevent UI flicker during transitions.