Structural directives are the tools you use to shape the reality of your user interface, conditionally building or repeating parts of the page based on your state.
1The Removal Mechanism
It is a common misconception that *ngIf simply hides elements. In reality, it physically removes the element and its children from the DOM tree. This is a powerful performance optimization: if a large part of your page isn't needed, Angular doesn't waste resources keeping it in memory or checking it for changes. When the condition becomes true, Angular recreates the elements from scratch based on the template.
2The <ng-container> Trick
Angular has a strict rule: only one structural directive per element. This can be frustrating when you want to loop through a list AND check a condition on each item. The solution is <ng-container>. This is a logical grouping element that doesn't appear in the final HTML. By wrapping your code in an <ng-container>, you can apply one directive to the container and another to the element inside, keeping your DOM clean and valid.
3Step-by-Step Breakdown
Structural directives are the architects of your template. They physically add, remove, or repeat elements in the DOM. Let's start with *ngIf.
*ngIf allows for conditional rendering. If the expression is false, Angular removes the element from the DOM entirely—it doesn't just hide it with CSS.
You can also use an 'else' block by providing a reference to an <ng-template>. This makes your code cleaner and more efficient.
Checkpoint: Does *ngIf hide an element using 'display: none' or physically remove it from the DOM?
- →Hides it with CSS
- →Removes it from the DOM
Next is *ngFor. It repeats an element for each item in a list. It also provides useful variables like 'index', 'first', and 'last'.
Finally, *ngSwitch is used for multiple conditions. It's like a switch statement in code, but inside your HTML.
Checkpoint: Which structural directive is used to iterate over an array and repeat an HTML element?
- →*ngIf
- →*ngFor
- →*ngSwitch
Remember: You can only have ONE structural directive per element. If you need both *ngIf and *ngFor, use a wrapper like <ng-container>.
Magnificent! You've mastered the building blocks of dynamic layouts. Next, we'll see how to decorate elements with Attribute Directives.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1`*ngFor`-Generated Lists Should Render as Real Semantic Lists
Repeating `<li>` elements with `*ngFor` inside a `<ul>` gives screen readers a real list with an accurate item count; repeating `<div>`s with the same data does not — the structural directive's job is data repetition, but the surrounding tags still determine the semantics.
2Track By Function Prevents Focus Loss During List Re-Renders
Without a `trackBy` function, Angular may destroy and recreate DOM nodes unnecessarily when a list updates, which can silently steal keyboard focus from an element a user was actively interacting with — `trackBy` lets Angular correctly reuse existing DOM nodes instead.
SEO Implications
- 1
Structural Directives Don't Change Crawlability — Only Whether Content Exists in the Rendered DOM
`*ngIf`/`*ngFor` genuinely add or remove elements from the DOM; a crawler only sees whatever the resolved condition/data produces at render time, which under SSR must reflect real data available during the server-side pass, not client-only state.
- 2
Avoid `*ngFor` Rendering the Same Conceptual List Differently Across Server and Client
If server-rendered and client-hydrated data differ even slightly (a different sort order, a different page of results), Angular's hydration can mismatch, and crawlers may capture an inconsistent snapshot — keep server and initial client data sources consistent.
Best Practices
Always Provide a `trackBy` Function for `*ngFor` Over Dynamic Data
Without it, Angular's default tracking falls back to object identity, which can cause the entire list to be torn down and rebuilt on any data refresh, even when most items are unchanged — a `trackBy` function keyed on a stable ID (like `item.id`) lets Angular correctly diff and reuse existing DOM nodes.
Use `<ng-container>` to Apply Structural Directives Without Adding Extra DOM Nodes
When you need `*ngIf` or `*ngFor` purely for logic but don't want an extra wrapping element in the rendered output (which could interfere with CSS Grid/Flexbox layout or ARIA structure), `<ng-container>` applies the directive without adding any real element to the DOM.
Frequent Bugs
Updating a list rendered with `*ngFor` causes visible flickering or loses the focused/scrolled item.
The `*ngFor` is missing a `trackBy` function, so Angular falls back to identity-based tracking and may unnecessarily destroy and recreate DOM nodes for items that conceptually didn't change. Add `trackBy` keyed on a stable unique identifier from each item.
Wrapping a group of elements in `*ngIf` to conditionally show them together adds an unwanted extra `<div>` to the rendered output.
Use `<ng-container *ngIf="condition">` instead of a `<div *ngIf="condition">` — `<ng-container>` is a purely logical grouping construct that never renders as a real DOM element, avoiding the extra wrapping node while still applying the structural directive to everything inside it.
Real-World Examples
Performant List Rendering With trackBy
A frequently-updating list of live scores uses `trackBy` to ensure only genuinely changed items are re-rendered, preventing flicker and preserving any element a user has focused or scrolled to.
<li *ngFor="let score of scores; trackBy: trackByGameId">{{ score.value }}</li>
trackByGameId(index: number, score: Score) { return score.gameId; }