Lifecycle Hooks
A component has a life: Angular creates it, sets its inputs, renders it, updates it many times and finally destroys it when it leaves the page. Lifecycle hooks are methods and functions that Angular calls at these moments, so you can run code at the right time, such as loading data when inputs are ready or stopping a timer when the component disappears.
Signals have made many hooks less necessary, but you will still use a few every day and meet all of them in existing code.
The order of events
Here is a component that records every hook it goes through. Its parent passes a roll number, then changes it, then removes the component:
import {
AfterViewInit, Component, OnChanges, OnDestroy, OnInit, SimpleChanges,
afterNextRender, input,
} from '@angular/core';
@Component({
selector: 'app-hook-logger',
template: `<p>Roll no: {{ rollNo() }}</p>`,
})
export class HookLogger implements OnChanges, OnInit, AfterViewInit, OnDestroy {
readonly rollNo = input.required<number>();
constructor() {
console.log('constructor');
afterNextRender(() => console.log('afterNextRender'));
}
ngOnChanges(changes: SimpleChanges) {
console.log(`ngOnChanges: rollNo = ${changes['rollNo']?.currentValue}`);
}
ngOnInit() {
console.log(`ngOnInit: rollNo = ${this.rollNo()}`);
}
ngAfterViewInit() {
console.log('ngAfterViewInit');
}
ngOnDestroy() {
console.log('ngOnDestroy');
}
}constructor ngOnChanges: rollNo = 21 ngOnInit: rollNo = 21 ngAfterViewInit afterNextRender --- parent changes roll number to 22 ngOnChanges: rollNo = 22 --- parent removes the component ngOnDestroy
Implementing the interfaces (OnInit, OnDestroy and so on) is optional, but it makes TypeScript check that you spelled the method names correctly.
The hooks you need most
| Hook | When it runs | Typical use |
|---|---|---|
constructor | The class is created; inputs are NOT set yet | inject() services, create signals and effects |
ngOnChanges | Before ngOnInit and whenever an input changes | Older code reacting to input changes |
ngOnInit | Once, after the first inputs are set | Start work that needs input values |
ngAfterViewInit | Once, after the component's view is created | Older code reading @ViewChild |
ngOnDestroy | Just before the component is removed | Stop timers, unsubscribe, save drafts |
There are also ngDoCheck, ngAfterContentInit, ngAfterContentChecked and ngAfterViewChecked. They are rarely needed in new code; avoid ngDoCheck and the Checked hooks in particular, because they run on every check and can slow the app down.
Constructor vs ngOnInit
Beginners often ask where to put start-up code. The rule is simple:
- The constructor is for setting up the object: injecting services, creating signals,
computedvalues and effects. Do not read inputs here; they have no value yet. - ngOnInit runs once after Angular has set the inputs, so it is the place for one-time work that needs them, such as logging a page view with a product ID.
With signal inputs, you often need neither: a computed or effect created in the constructor reads the input when it is ready and re-runs when it changes. That is also why ngOnChanges is now rare: computed(() => this.rollNo() * 2) already follows the input.
Cleaning up with DestroyRef
Anything your component starts and does not stop on its own (an interval, a WebSocket, an event listener on window) must be stopped when the component is destroyed, or it keeps running in the background. This is called a memory leak.
ngOnDestroy works, but the modern way is DestroyRef, which lets you register clean-up right next to the code that needs it:
import { Component, DestroyRef, inject, signal } from '@angular/core';
@Component({
selector: 'app-live-clock',
template: `<p>{{ time() }}</p>`,
})
export class LiveClock {
protected time = signal(new Date().toLocaleTimeString('en-IN'));
constructor() {
const id = setInterval(() => this.time.set(new Date().toLocaleTimeString('en-IN')), 1000);
inject(DestroyRef).onDestroy(() => clearInterval(id));
}
}inject(DestroyRef) must be called in the injection context (the constructor or a field initializer). Effects created in a component already clean up automatically, as you saw in effect & linkedSignal.
Render hooks: afterNextRender and afterEveryRender
Some work must happen only after Angular has updated the real DOM: measuring an element, focusing an input, or starting a third-party library such as a chart or map that needs a real element. Angular provides functions for this:
afterNextRender(fn)runsfnonce, after the next time Angular renders.afterEveryRender(fn)runsfnafter every render. Use it rarely, and keep it fast.afterRenderEffect(fn)is likeeffect, but runs after rendering and only when the signals it reads have changed.
import { Component, ElementRef, afterNextRender, inject } from '@angular/core';
@Component({
selector: 'app-seat-map',
template: `<canvas width="400" height="200"></canvas>`,
})
export class SeatMap {
private host = inject(ElementRef<HTMLElement>);
constructor() {
afterNextRender(() => {
const canvas = this.host.nativeElement.querySelector('canvas');
const ctx = canvas?.getContext('2d');
ctx?.fillRect(10, 10, 40, 40); // draw the first seat
});
}
}These functions never run during server-side rendering, because there is no real browser DOM on the server. That makes them the safe place for code that uses window, document or canvas APIs.
Common mistakes
- Reading inputs in the constructor. They are not set yet; a required input throws an error. Use
ngOnInit,computedoreffect. - Forgetting to stop timers. An interval in a removed component keeps running and updating signals. Clear it with
DestroyRef.onDestroyorngOnDestroy. - Touching the DOM in
ngOnInit. The view is not rendered yet, and the code breaks on the server. UseafterNextRender. - Heavy work in
afterEveryRenderorngDoCheck. These run very often; keep them tiny or avoid them.
Key points
- Angular calls hooks in order: constructor,
ngOnChanges,ngOnInit,ngAfterViewInit, render hooks, and finallyngOnDestroy. - Use the constructor for injection and creating signals; use
ngOnInitfor one-time work that needs inputs. - With signal inputs,
computedandeffectreplace most uses ofngOnChanges. - Register clean-up with
inject(DestroyRef).onDestroy(...)orngOnDestroy. - Use
afterNextRenderfor DOM work; it does not run during server-side rendering.
Exercise
Write a RecordingIndicator component for an online class that shows "Recording: X s" and counts up every second. Start the timer in the constructor and stop it with DestroyRef. In the parent, add a button that toggles the indicator on and off with @if, and check in the browser console (add a console.log in the interval) that the counting stops when the indicator is removed.
Show solution
import { Component, DestroyRef, inject, signal } from '@angular/core';
@Component({
selector: 'app-recording-indicator',
template: `<p>Recording: {{ seconds() }} s</p>`,
})
export class RecordingIndicator {
protected seconds = signal(0);
constructor() {
const id = setInterval(() => {
this.seconds.update((s) => s + 1);
console.log('tick', this.seconds());
}, 1000);
inject(DestroyRef).onDestroy(() => clearInterval(id));
}
}
@Component({
selector: 'app-class-room',
imports: [RecordingIndicator],
template: `
<button (click)="recording.set(!recording())">
{{ recording() ? 'Stop' : 'Start' }} recording
</button>
@if (recording()) {
<app-recording-indicator />
}
`,
})
export class ClassRoom {
protected recording = signal(false);
}When recording becomes false, @if destroys the indicator, Angular runs the onDestroy callback, and the "tick" messages stop. Starting again creates a fresh component whose count begins at 0.
Quick Quiz
1. Which runs first when a component is created?
The constructor runs when the class is created, before Angular sets inputs or calls any hook.
2. Where should you start one-time work that needs input values?
ngOnInit runs once after the first input values are set.
3. What is the modern way to register clean-up code in a component?
DestroyRef.onDestroy runs its callback when the component is destroyed.
4. Which function is safe for measuring or drawing on DOM elements, and is skipped on the server?
Render hooks run after the DOM is updated and only in the browser.
Learn Angular with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
