Angular Lifecycle
Read one Angular interview question at a time, then flip for the complete explanation, example, and code.
Read one Angular interview question at a time, then flip for the complete explanation, example, and code.
Angular Interview Questions · Angular Lifecycle Companion Article
Angular components are not created once and then left unchanged. A component passes through several stages: Angular creates it, initializes its inputs, checks it for changes, renders its content and view, updates it when application state changes, and finally destroys it when it is no longer required.
Angular provides lifecycle hooks that allow developers to run code at specific points during this process.
Understanding the lifecycle is important when working with:
A component lifecycle describes everything that happens from the moment Angular creates a component until that component is destroyed.
The main lifecycle stages are:
The commonly used lifecycle methods and render callbacks are:
constructor
↓
ngOnChanges
↓
ngOnInit
↓
ngDoCheck
↓
ngAfterContentInit
↓
ngAfterContentChecked
↓
ngAfterViewInit
↓
ngAfterViewChecked
↓
DOM Render
↓
afterNextRender / afterEveryRender
↓
ngOnDestroy
During initial component creation, ngOnChanges runs before ngOnInit when the component has inputs. Hooks such as ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked can run repeatedly during later change-detection cycles.
| Lifecycle API | Runs | Common Purpose |
|---|---|---|
constructor | When the class instance is created | Dependency injection and basic initialization |
ngOnChanges | When input values change | React to parent-provided data |
ngOnInit | Once after initial inputs are initialized | Component initialization |
ngDoCheck | During component change checking | Custom change detection |
ngAfterContentInit | Once after projected content initializes | Work with content queries |
ngAfterContentChecked | After projected content is checked | Observe projected-content changes |
ngAfterViewInit | Once after component view initializes | Work with view elements |
ngAfterViewChecked | After component view is checked | Observe view updates |
afterNextRender | Once after the next application render | One-time DOM work |
afterEveryRender | After every application render | Repeated DOM-related work |
ngOnDestroy | Once before destruction | Cleanup resources |
constructorThe constructor is technically a JavaScript/TypeScript class constructor rather than an Angular lifecycle hook.
Angular calls it when creating the component instance.
A constructor is commonly used for:
Example:
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user',
template: `<p>User Component</p>`
})
export class UserComponent {
constructor(private userService: UserService) {
console.log('Component instance created');
}
}
Avoid putting large amounts of component initialization logic inside the constructor.
For example, avoid:
constructor(private userService: UserService) {
this.userService.loadUsers().subscribe(...);
this.calculateDashboard();
this.initializePage();
}
Component initialization that depends on initialized inputs generally belongs in ngOnInit.
A useful rule is:
constructor → create the class and inject dependencies
ngOnInit → initialize component behavior
ngOnInitngOnInit() runs once after Angular has initialized the component's input values.
It is one of the most commonly used lifecycle hooks.
Typical uses include:
Example:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product',
template: `<h2>{{ title }}</h2>`
})
export class ProductComponent implements OnInit {
title = '';
ngOnInit(): void {
this.title = 'Products';
}
}
A common API-related example is:
ngOnInit(): void {
this.loadProducts();
}
loadProducts(): void {
this.productService.getProducts().subscribe(products => {
this.products = products;
});
}
ngOnInit executes once for each component instance. It does not execute again every time Angular checks the component.
constructor vs ngOnInitThese two are often confused.
constructor | ngOnInit |
|---|---|
| JavaScript/TypeScript class feature | Angular lifecycle hook |
| Runs when instance is created | Runs after initial inputs are initialized |
| Best for dependency injection | Best for component initialization |
| Inputs should not be relied upon as initialized component state | Initial input values are available |
| Runs first | Runs later |
Example:
constructor() {
console.log('Constructor');
}
ngOnInit(): void {
console.log('ngOnInit');
}
Output:
Constructor
ngOnInit
ngOnChangesngOnChanges() allows a component to react when one or more of its input properties change.
Consider a child component:
@Component({
selector: 'app-user',
template: `<p>{{ userName }}</p>`
})
export class UserComponent {
@Input() userName = '';
}
When the parent changes userName, Angular can call ngOnChanges.
import {
Component,
Input,
OnChanges,
SimpleChanges
} from '@angular/core';
export class UserComponent implements OnChanges {
@Input() userName = '';
ngOnChanges(changes: SimpleChanges): void {
console.log(changes);
}
}
Each changed input is represented by a SimpleChange.
It provides information such as:
Example:
ngOnChanges(changes: SimpleChanges): void {
if (changes['userName']) {
console.log(
'Previous:',
changes['userName'].previousValue
);
console.log(
'Current:',
changes['userName'].currentValue
);
console.log(
'First Change:',
changes['userName'].firstChange
);
}
}
A practical use case is recalculating something whenever an input changes:
@Input() price = 0;
@Input() quantity = 0;
ngOnChanges(): void {
this.total = this.price * this.quantity;
}
During initialization, the first ngOnChanges occurs before ngOnInit.
ngDoCheckngDoCheck() runs whenever Angular checks the component for changes.
It gives developers an opportunity to implement custom checking logic.
Example:
import { Component, DoCheck } from '@angular/core';
export class CartComponent implements DoCheck {
ngDoCheck(): void {
console.log('Component checked');
}
}
This hook can execute very frequently.
For that reason, code inside it should be extremely lightweight.
Avoid expensive operations such as:
ngDoCheck(): void {
this.calculateThousandsOfRecords();
}
This could significantly reduce application performance because Angular may call the method many times.
Use ngDoCheck only when Angular's normal reactive mechanisms cannot conveniently handle the required change.
Angular's current guidance specifically warns that ngDoCheck runs frequently and should generally be avoided unless necessary.
ngAfterContentInitTo understand ngAfterContentInit, first understand projected content.
A reusable component may contain:
<ng-content></ng-content>
The parent can provide content:
<app-card>
<h2>Angular Lifecycle</h2>
</app-card>
The <h2> is content projected into app-card.
ngAfterContentInit() executes once after Angular initializes this projected content.
Example:
import {
AfterContentInit,
Component
} from '@angular/core';
export class CardComponent implements AfterContentInit {
ngAfterContentInit(): void {
console.log('Projected content initialized');
}
}
It is especially useful when working with content queries such as:
@ContentChild(...)
A simple mental model is:
Content = HTML supplied from outside the component
View = HTML belonging to the component's own template
Angular warns against changing checked component state from this stage because doing so can result in ExpressionChangedAfterItHasBeenCheckedError.
ngAfterContentCheckedngAfterContentChecked() executes after Angular checks the content projected into the component.
Example:
import {
AfterContentChecked,
Component
} from '@angular/core';
export class CardComponent implements AfterContentChecked {
ngAfterContentChecked(): void {
console.log('Projected content checked');
}
}
Unlike ngAfterContentInit, this hook can run many times.
Compare:
ngAfterContentInit
↓
Runs once
ngAfterContentChecked
↓
May run repeatedly
Avoid expensive processing here.
For example, this is poor practice:
ngAfterContentChecked(): void {
this.processLargeDataset();
}
Hooks that execute during every change-detection cycle should normally contain minimal logic.
ngAfterViewInitngAfterViewInit() runs once after Angular has initialized the component's own view.
It is commonly associated with access to elements or child components from the component template.
Example template:
<input #searchBox type="text">
Component:
import {
AfterViewInit,
Component,
ElementRef,
ViewChild
} from '@angular/core';
export class SearchComponent implements AfterViewInit {
@ViewChild('searchBox')
searchBox!: ElementRef<HTMLInputElement>;
ngAfterViewInit(): void {
this.searchBox.nativeElement.focus();
}
}
Typical use cases include:
However, DOM-specific work can often be better expressed through Angular's modern render callbacks when the operation specifically needs the rendered DOM.
ngAfterViewInit executes only once for each created view. Angular also cautions against changing already-checked state from this hook because that can cause ExpressionChangedAfterItHasBeenCheckedError.
ngAfterViewCheckedngAfterViewChecked() executes after Angular checks the component's view.
Example:
import {
AfterViewChecked,
Component
} from '@angular/core';
export class DashboardComponent implements AfterViewChecked {
ngAfterViewChecked(): void {
console.log('View checked');
}
}
This method can execute frequently.
Therefore, avoid performing:
inside this hook.
For example, never treat it like an initialization hook:
ngAfterViewChecked(): void {
this.http.get('/api/products').subscribe();
}
That logic could execute repeatedly as Angular checks the view.
Angular recommends avoiding this hook unless there is no cleaner alternative because of its potential performance impact.
The difference between content and view is important.
Consider:
<app-panel>
<p>Projected paragraph</p>
</app-panel>
And inside app-panel:
<section>
<ng-content></ng-content>
<button>Save</button>
</section>
Here:
<p>Projected paragraph</p>
belongs to projected content.
But:
<section>
<button>
belong to the component's view.
Therefore:
ngAfterContentInit
ngAfterContentChecked
are related to projected content.
While:
ngAfterViewInit
ngAfterViewChecked
are related to the component's view.
ngOnDestroyngOnDestroy() executes once immediately before Angular destroys a component.
A component may be destroyed when:
@if condition removes itExample:
import {
Component,
OnDestroy
} from '@angular/core';
export class NotificationComponent implements OnDestroy {
ngOnDestroy(): void {
console.log('Component destroyed');
}
}
Its main purpose is cleanup.
Examples of resources that may require cleanup include:
Angular calls ngOnDestroy once before destroying the component instance.
Cleanup is one of the most important parts of component lifecycle management.
Suppose a component creates an interval:
timerId!: ReturnType<typeof setInterval>;
ngOnInit(): void {
this.timerId = setInterval(() => {
console.log('Running');
}, 1000);
}
If the component is destroyed but the interval continues running, unnecessary work continues in the background.
The interval should therefore be cleared:
ngOnDestroy(): void {
clearInterval(this.timerId);
}
The complete pattern becomes:
export class TimerComponent implements OnInit, OnDestroy {
timerId!: ReturnType<typeof setInterval>;
ngOnInit(): void {
this.timerId = setInterval(() => {
console.log('Running');
}, 1000);
}
ngOnDestroy(): void {
clearInterval(this.timerId);
}
}
Manual event listeners should also be removed when they are no longer needed.
Example:
private handleResize = () => {
console.log(window.innerWidth);
};
ngOnInit(): void {
window.addEventListener('resize', this.handleResize);
}
ngOnDestroy(): void {
window.removeEventListener('resize', this.handleResize);
}
Without cleanup, callbacks can continue referencing resources that should no longer be used.
Some manually created RxJS subscriptions may need to be unsubscribed.
Traditional example:
private subscription!: Subscription;
ngOnInit(): void {
this.subscription =
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
Modern Angular applications can often use takeUntilDestroyed() instead of maintaining subscription variables manually.
DestroyRefDestroyRef provides a modern Angular API for registering cleanup logic.
Instead of placing every cleanup operation inside one large ngOnDestroy() method, cleanup can be registered close to the code that creates the resource.
Example:
import {
Component,
DestroyRef,
inject
} from '@angular/core';
@Component({
selector: 'app-timer',
template: `<p>Timer</p>`
})
export class TimerComponent {
private destroyRef = inject(DestroyRef);
constructor() {
const timerId = setInterval(() => {
console.log('Running');
}, 1000);
this.destroyRef.onDestroy(() => {
clearInterval(timerId);
});
}
}
This keeps setup and cleanup logic together:
Create resource
↓
Register cleanup
↓
Component destroyed
↓
Cleanup automatically executed
DestroyRef.onDestroy() registers a callback in the current destruction scope. DestroyRef also exposes a destroyed property that can be checked when asynchronous code might finish after the component has already been destroyed.
DestroyRef with Asynchronous LogicConsider delayed work:
private destroyRef = inject(DestroyRef);
loadData(): void {
setTimeout(() => {
if (this.destroyRef.destroyed) {
return;
}
this.message = 'Data loaded';
}, 3000);
}
Checking the destroyed state can help prevent code from trying to interact with a component that no longer exists.
takeUntilDestroyed()Angular also provides takeUntilDestroyed() for RxJS integration.
It completes the observable subscription when the associated Angular destruction context is destroyed.
Example:
import { Component } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-user-list',
template: `...`
})
export class UserListComponent {
constructor(private userService: UserService) {
this.userService.getUsers()
.pipe(takeUntilDestroyed())
.subscribe(users => {
console.log(users);
});
}
}
This avoids manually writing:
private subscription!: Subscription;
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
For many Angular/RxJS scenarios, this results in simpler lifecycle cleanup.
afterNextRenderafterNextRender() is a modern Angular render callback.
It executes after Angular next finishes rendering the application to the DOM.
Unlike methods such as ngAfterViewInit, it is:
Example:
import {
afterNextRender,
Component
} from '@angular/core';
@Component({
selector: 'app-profile',
template: `<div>Profile</div>`
})
export class ProfileComponent {
constructor() {
afterNextRender(() => {
console.log('Next render completed');
});
}
}
A typical use case is initializing a third-party JavaScript library after the DOM has been rendered.
afterNextRender must normally be registered from an Angular injection context, such as a component constructor. Render callbacks run in browser environments and do not execute during server-side rendering or build-time pre-rendering.
afterNextRenderUse it when something should happen once after the next completed render.
Examples include:
Conceptually:
Component created
↓
Angular performs rendering
↓
DOM updated
↓
afterNextRender callback
afterEveryRenderafterEveryRender() executes after each completed Angular application render.
Example:
import {
afterEveryRender,
Component
} from '@angular/core';
@Component({
selector: 'app-dashboard',
template: `<div>Dashboard</div>`
})
export class DashboardComponent {
constructor() {
afterEveryRender(() => {
console.log('Application rendered');
});
}
}
The important difference is:
afterNextRender
↓
Runs once after the next render
afterEveryRender
↓
Runs after every render
Because afterEveryRender can run repeatedly, the callback should be lightweight.
Like afterNextRender, it is a render callback rather than a traditional component lifecycle method, requires an appropriate injection context when registered normally, and runs only on browser platforms.
Modern Angular render callbacks support phases for organizing DOM work.
The available phases are:
earlyReadwritemixedReadWritereadAngular recommends separating DOM writes and reads when possible because repeatedly mixing layout reads and writes can hurt browser rendering performance.
Example:
afterNextRender({
write: () => {
this.element.nativeElement.style.height = '200px';
return true;
},
read: (changed) => {
if (changed) {
const height =
this.element.nativeElement
.getBoundingClientRect()
.height;
console.log(height);
}
}
});
The basic idea is:
Write DOM changes first
↓
Browser layout
↓
Read measurements afterward
This can help avoid unnecessary layout recalculation.
afterNextRender vs afterEveryRender| Feature | afterNextRender | afterEveryRender |
|---|---|---|
| Execution | Once | Repeatedly |
| Trigger | Next completed render | Every completed render |
| Type | Standalone function | Standalone function |
| DOM work | Yes | Yes |
| Browser only | Yes | Yes |
| SSR execution | No | No |
| Typical purpose | Initialize something once | Respond to repeated rendering |
ngAfterViewInit vs afterNextRenderThese APIs can appear similar but solve different problems.
ngAfterViewInitUse it when your logic is directly related to initialization of that component's view or view queries.
ngAfterViewInit(): void {
console.log(this.childComponent);
}
afterNextRenderUse it when work should happen after the Angular application has completed its next DOM render.
constructor() {
afterNextRender(() => {
console.log('DOM rendering finished');
});
}
A practical guideline is:
Need initialized component view/query?
→ ngAfterViewInit
Need work after actual application DOM rendering?
→ afterNextRender
The following component demonstrates traditional lifecycle hooks together:
import {
AfterContentChecked,
AfterContentInit,
AfterViewChecked,
AfterViewInit,
Component,
DoCheck,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges
} from '@angular/core';
@Component({
selector: 'app-lifecycle-demo',
template: `<p>{{ message }}</p>`
})
export class LifecycleDemoComponent
implements
OnChanges,
OnInit,
DoCheck,
AfterContentInit,
AfterContentChecked,
AfterViewInit,
AfterViewChecked,
OnDestroy {
@Input() message = '';
constructor() {
console.log('constructor');
}
ngOnChanges(changes: SimpleChanges): void {
console.log('ngOnChanges', changes);
}
ngOnInit(): void {
console.log('ngOnInit');
}
ngDoCheck(): void {
console.log('ngDoCheck');
}
ngAfterContentInit(): void {
console.log('ngAfterContentInit');
}
ngAfterContentChecked(): void {
console.log('ngAfterContentChecked');
}
ngAfterViewInit(): void {
console.log('ngAfterViewInit');
}
ngAfterViewChecked(): void {
console.log('ngAfterViewChecked');
}
ngOnDestroy(): void {
console.log('ngOnDestroy');
}
}
A typical initial sequence is:
constructor
ngOnChanges
ngOnInit
ngDoCheck
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
On later checks, hooks such as these can execute again:
ngOnChanges → when relevant inputs changed
ngDoCheck
ngAfterContentChecked
ngAfterViewChecked
Finally:
ngOnDestroy
executes before Angular destroys the component.
Suppose the parent contains:
<app-child [name]="userName"></app-child>
When Angular creates the child:
Child constructor
↓
Input value assigned
↓
Child ngOnChanges
↓
Child ngOnInit
↓
Child view initialized
Later the parent changes:
this.userName = 'Rahul';
The child already exists, so Angular does not create it again.
Instead:
Input changed
↓
ngOnChanges
↓
Normal change checking
This distinction is important.
Changing an input does not recreate the component.
@ifConsider:
@if (showProfile) {
<app-profile />
}
When:
showProfile = true;
Angular creates the component and its initialization lifecycle begins.
When:
showProfile = false;
Angular removes the component and its destruction lifecycle runs.
Conceptually:
showProfile = true
↓
Component created
↓
Initialization hooks
showProfile = false
↓
Component removed
↓
ngOnDestroy / DestroyRef cleanup
Avoid:
ngAfterViewChecked(): void {
this.loadUsers();
}
This can cause repeated API requests.
Prefer initialization or explicit reactive logic.
Avoid treating the constructor as the component's main initialization method.
Prefer:
constructor(private service: UserService) {}
ngOnInit(): void {
this.loadUsers();
}
If you manually create:
intervals
listeners
observers
subscriptions
third-party objects
consider how they should be destroyed.
Use:
ngOnDestroy
DestroyRef
takeUntilDestroyed
where appropriate.
ngDoCheckBecause ngDoCheck can run frequently, complex processing can create serious performance problems.
Avoid:
ngDoCheck(): void {
this.products
.filter(...)
.map(...)
.sort(...);
}
Prefer computed or reactive approaches where possible.
Hooks such as:
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
occur during sensitive stages of Angular's checking process.
Changing template-bound state from these stages can lead to Angular's ExpressionChangedAfterItHasBeenCheckedError.
Angular specifically cautions against state changes from these hooks after values have already been checked.
Do not rely on:
afterNextRender
afterEveryRender
for server-side rendering logic.
These callbacks execute on browser platforms rather than during SSR or build-time pre-rendering.
Use:
ngOnInit
Use:
ngOnChanges
Consider:
ngDoCheck
but use it sparingly.
Use:
ngAfterContentInit
or, rarely:
ngAfterContentChecked
Use:
ngAfterViewInit
Use:
ngAfterViewChecked
only when genuinely required.
Use:
afterNextRender
Use:
afterEveryRender
Use:
ngOnDestroy
DestroyRef
takeUntilDestroyed
depending on the scenario.
Consider a dashboard that:
A simplified implementation could look like:
import {
afterNextRender,
Component,
DestroyRef,
inject,
Input,
OnChanges,
SimpleChanges
} from '@angular/core';
@Component({
selector: 'app-dashboard',
template: `
<h2>{{ userName }}</h2>
<div id="chart"></div>
`
})
export class DashboardComponent implements OnChanges {
@Input() userId = 0;
userName = '';
private destroyRef = inject(DestroyRef);
constructor() {
const timerId = setInterval(() => {
console.log('Refreshing dashboard');
}, 5000);
this.destroyRef.onDestroy(() => {
clearInterval(timerId);
});
afterNextRender(() => {
console.log('Initialize chart here');
});
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['userId']) {
this.loadUser(this.userId);
}
}
loadUser(id: number): void {
console.log('Loading user:', id);
}
}
This example demonstrates that lifecycle APIs should be selected according to responsibility:
Input changes
→ ngOnChanges
Rendered DOM required
→ afterNextRender
Cleanup required
→ DestroyRef
Lifecycle hooks themselves are not automatically expensive.
Performance problems usually happen because developers place expensive code inside hooks that run frequently.
Be especially careful with:
ngDoCheck
ngAfterContentChecked
ngAfterViewChecked
afterEveryRender
Avoid unnecessary:
inside frequently executing callbacks.
A lifecycle method should have a clear reason to exist.
Do not implement lifecycle hooks merely because Angular provides them.
ngOnInit for initialization that depends on initial component state.ngOnChanges when behavior must respond specifically to input changes.ngDoCheck unless custom change checking is genuinely required.ngAfterContentChecked and ngAfterViewChecked lightweight.ngAfterViewInit when working with initialized view queries.afterNextRender for one-time work requiring the rendered DOM.afterEveryRender only when work genuinely needs to happen after every render.DestroyRef when keeping setup and cleanup logic together makes the code easier to understand.takeUntilDestroyed() for Angular-aware RxJS subscription cleanup.Instead of memorizing every method independently, remember lifecycle responsibilities:
CREATE
│
├── constructor
│
INITIALIZE DATA
│
├── ngOnChanges
├── ngOnInit
│
CHECK
│
├── ngDoCheck
│
CONTENT
│
├── ngAfterContentInit
├── ngAfterContentChecked
│
VIEW
│
├── ngAfterViewInit
├── ngAfterViewChecked
│
RENDERED DOM
│
├── afterNextRender
├── afterEveryRender
│
DESTROY
│
├── ngOnDestroy
└── DestroyRef cleanup
This model makes it easier to choose the correct API based on when your code needs to run, instead of trying to memorize lifecycle names without understanding their purpose.
Angular lifecycle APIs provide controlled points for running code during component creation, change checking, rendering, and destruction.
The most commonly useful lifecycle concepts are:
constructor for creating the class and injecting dependenciesngOnInit for initial setupngOnChanges for reacting to input changesngAfterContentInit for projected contentngAfterViewInit for initialized viewsafterNextRender for one-time post-render DOM workafterEveryRender for repeated post-render workngOnDestroy for final cleanupDestroyRef for modern destruction callbackstakeUntilDestroyed() for convenient RxJS cleanupThe key is not to use every lifecycle hook. Good Angular components use only the lifecycle APIs required by their actual behavior and keep frequently executed hooks as lightweight as possible.