*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.
Use this Angular interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.
How does *ngIf affect the DOM and component lifecycle?Frontend interview answer
This Angular interview question tests whether you can explain Angular *ngIf in production: DOM teardown, lifecycle resets, and hide vs destroy, connect it to production trade-offs, and handle common follow-up questions.
- Angular *ngIf in production: DOM teardown, lifecycle resets, and hide vs destroy explanation without falling back to memorized docs wording
- Directives and Ngif reasoning, edge cases, and production failure modes
- How you would answer the most likely Angular interview follow-up
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 |
<!-- destroy + recreate every time -->
<app-profile-form *ngIf="showForm"></app-profile-form>
<!-- keep the same child instance alive -->
<app-profile-form [hidden]="!showForm"></app-profile-form>
<!-- If the child keeps local draft text or component-scoped providers,
the *ngIf version recreates them on every show -->
Worked toggle scenario
If a child component keeps local draft text, a pending timer, or a component-scoped service, flipping *ngIf false tears all of that down. Flipping it true creates a fresh child, reruns init hooks, and starts new subscriptions or async-pipe subscriptions inside that subtree. Use hiding when the requirement is "keep the current instance and its local state alive"; use *ngIf when the requirement is real teardown.
Interview pivots after *ngIf
Once you explain that *ngIf creates and destroys embedded views, interviewers often branch into nearby Angular topics. The clean way to answer them is to keep the same lens: what survives teardown, what gets recreated, and which concern is actually separate from conditional view existence.
Follow-up | Short answer | Go deeper |
|---|---|---|
Memory leaks | Because | Angular memory leaks: |
Change detection | A subtree removed by | Angular change detection strategies: |
Constructor vs | When | Angular constructor vs ngOnInit: |
Routing | Routing swaps routed views based on the URL; | Angular routing: |
Two-way binding | Two-way binding solves value synchronization, not instance preservation. A control under | Angular custom two-way binding: |
Structural directives |
| Structural vs attribute directives: |
Performance | Destroying a heavy branch with | Angular performance optimization: |
Dependency injection | If a provider lives at the component level, | Angular dependency injection: |
Angular services | This is the practical boundary interviewers care about: local component state disappears with | Angular services: |
NgRx | Store state survives because it lives outside the torn-down child view. What resets under | NgRx data flow: |
Lazy loading | Lazy loading decides when Angular fetches code for a route or feature boundary; | Angular lazy loading: |
Angular vs AngularJS | Modern Angular | Angular vs AngularJS: |
Custom directives | If the discussion turns into "how would you build this yourself?", the senior answer is | Angular directives: |
*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 this as one explanation rep, then continue with the Angular interview questions cluster or a guided prep path.