*ngIf is not a visibility toggle; it creates and destroys embedded views. The practical angle is lifecycle and debug behavior: state resets, ngOnDestroy cleanup, and the production choice between real teardown and simply hiding UI.
How does *ngIf affect the DOM and component lifecycle?
Debug mental model*ngIf does not hide a subtree; it creates and destroys an embedded view. In production, that difference shows up as reset form state, fresh child instances, repeated init work, and cleanup bugs when subscriptions or listeners are tied to that subtree.
<!-- Shorthand -->
<app-child *ngIf="show"></app-child>
<!-- Roughly equivalent to -->
<ng-template [ngIf]="show">
<app-child></app-child>
</ng-template>
When condition flips | DOM result | Instance result | Lifecycle impact |
|---|---|---|---|
false → true | Nodes are inserted into the DOM | New component/directive instances are created | constructor → (ngOnChanges) → ngOnInit → view/content hooks |
true → false | Nodes are removed from the DOM | Instances are destroyed | ngOnDestroy runs; subscriptions/cleanup should happen here |
true → true (condition stays true) | DOM stays | Same instances stay | Normal change detection updates bindings |
// child.component.ts
import { Component, OnDestroy, OnInit } from '@angular/core';
@Component({
selector: 'app-child',
template: `Child is alive`
})
export class ChildComponent implements OnInit, OnDestroy {
constructor() {
console.log('child: constructor');
}
ngOnInit(): void {
console.log('child: ngOnInit');
}
ngOnDestroy(): void {
console.log('child: ngOnDestroy');
}
}
// parent template
// <button (click)="show = !show">toggle</button>
// <app-child *ngIf="show"></app-child>
Topic | *ngIf behavior | Practical consequence |
|---|---|---|
Component state | Reset on every re-create | Local fields, form state, and child component state are lost on hide/show |
Subscriptions/listeners | Destroyed view stops emitting into template | If you subscribe manually, you must unsubscribe in ngOnDestroy (or use async pipe / takeUntilDestroyed) |
DI scope | Component-level providers are torn down with the view | A service provided in the component gets a fresh instance on next show |
View queries | ViewChild/ContentChild inside the block disappear | References become undefined when view is destroyed; guard access |
Change detection cost | Removed subtree is not checked | Hiding heavy UI with *ngIf can reduce CD + DOM updates |
Technique | Does it remove from DOM? | Does it destroy component instances? | When to prefer |
|---|---|---|---|
*ngIf | Yes | Yes | When you want real teardown (free resources, remove listeners, skip CD) |
[hidden] / CSS (display:none) | No | No | When you need to preserve state and avoid re-creation cost |
ng-container + else template | Same as *ngIf | Same as *ngIf | When you want clean conditional branching without extra wrapper DOM |
Common gotcha | What happens | Fix |
|---|---|---|
Toggling a form with *ngIf | User input resets on every hide/show | Use [hidden] if you must preserve state, or persist the form model outside the child |
Expensive init in ngOnInit | Runs every time the view is recreated | Cache in a service/facade, or keep component alive and just hide it |
Memory leaks | Manual subscriptions keep running if not cleaned up | Use async pipe, takeUntilDestroyed(), or ngOnDestroy cleanup |
*ngIf creates/destroys an embedded view. True = build DOM + instantiate components/directives and run init hooks. False = remove DOM + destroy instances and run ngOnDestroy. Use it when you want real teardown and to skip change detection for that subtree; use hiding when you need to keep state alive.
Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.