Angular Modern Lifecycle: afterRender Hooks & DestroyRef
🎬 The Story of the Smart House
Imagine you’re building a smart house that knows exactly when things happen.
- When someone moves in, the house sets up their room perfectly
- When a room is painted fresh, the house adjusts the lighting
- When someone moves out, the house cleans up and saves energy
Angular’s Modern Lifecycle works the same way! It tells your code: “Hey, the screen just updated!” or “Time to clean up!”
🏠What is Modern Lifecycle?
Old Way: Angular had lifecycle hooks like ngAfterViewInit - but they ran once.
New Way: Angular 16+ gives us:
afterRenderhooks → Run code AFTER the screen updatesDestroyRef→ Clean up anywhere, not just inngOnDestroy
Think of it like this:
- Old hooks = A doorbell that rings once when you arrive
- New hooks = A smart sensor that notices every time the room changes
🎨 afterRender Hooks: Your Screen’s Best Friend
What Problem Does It Solve?
Sometimes you need to:
- Measure how big something is on screen
- Focus on an input box
- Start an animation
- Talk to a chart library
The catch? You can only do this AFTER Angular paints the screen!
The Four Phases
Angular’s render process has four phases, like building a LEGO house:
graph TD A["📝 earlyRead"] --> B["🔧 write"] B --> C["🧱 mixedReadWrite"] C --> D["📖 read"] style A fill:#e1f5fe style B fill:#fff3e0 style C fill:#f3e5f5 style D fill:#e8f5e9
| Phase | What It Does | When to Use |
|---|---|---|
| earlyRead | Reads the screen FIRST | Measure sizes before changes |
| write | Makes changes | Update DOM, add classes |
| mixedReadWrite | Both read & write | Rare - try to avoid |
| read | Reads the screen LAST | Final measurements |
đź“– earlyRead: The First Looker
Story: Like checking the weather BEFORE you get dressed.
import { afterRender } from '@angular/core';
export class MyComponent {
constructor() {
afterRender(() => {
// This runs AFTER every render
const box = document.getElementById('myBox');
console.log('Box width:', box?.offsetWidth);
}, { phase: AfterRenderPhase.EarlyRead });
}
}
When to Use earlyRead?
- Measure element sizes
- Check scroll positions
- Read any DOM property before making changes
đź”§ write: The Changer
Story: Like a painter who changes the room color.
import {
afterRender,
AfterRenderPhase
} from '@angular/core';
export class ButtonComponent {
constructor() {
afterRender(() => {
// Change the DOM
const btn = document.querySelector('.my-btn');
btn?.classList.add('fancy-animation');
}, { phase: AfterRenderPhase.Write });
}
}
When to Use write?
- Add CSS classes
- Set element properties
- Start animations
- Initialize third-party libraries
đź§± mixedReadWrite: The Careful One
Story: Like rearranging furniture while measuring - tricky!
afterRender(() => {
// Read THEN write in same callback
const height = element.offsetHeight;
element.style.minHeight = `${height}px`;
}, { phase: AfterRenderPhase.MixedReadWrite });
⚠️ Warning
Mixed read/write can cause layout thrashing (slow performance). Try to separate reads and writes when possible!
đź“– read: The Final Check
Story: Like inspecting your room after cleaning.
afterRender(() => {
// Final measurements
const finalWidth = container.offsetWidth;
console.log('Final width:', finalWidth);
}, { phase: AfterRenderPhase.Read });
🎯 The Simpler Way: afterNextRender
Sometimes you only need to run code once after the first render.
import { afterNextRender } from '@angular/core';
export class ChartComponent {
constructor() {
afterNextRender(() => {
// Runs ONCE after first render
this.initializeChart();
});
}
initializeChart() {
console.log('Chart is ready!');
}
}
afterRender vs afterNextRender
| Feature | afterRender | afterNextRender |
|---|---|---|
| Runs | After EVERY render | After FIRST render only |
| Use Case | Continuous updates | One-time setup |
| Example | Track size changes | Initialize libraries |
đź§ą DestroyRef: The Smart Cleaner
The Old Problem
Before, cleanup only happened in ngOnDestroy:
// OLD WAY - cleanup stuck in one place
export class OldComponent implements OnDestroy {
private interval: any;
ngOnInit() {
this.interval = setInterval(() => {
console.log('tick');
}, 1000);
}
ngOnDestroy() {
clearInterval(this.interval);
}
}
The New Way with DestroyRef
Story: Like giving each toy its own cleanup robot!
import {
inject,
DestroyRef
} from '@angular/core';
export class ModernComponent {
private destroyRef = inject(DestroyRef);
startTimer() {
const interval = setInterval(() => {
console.log('tick');
}, 1000);
// Cleanup lives RIGHT HERE!
this.destroyRef.onDestroy(() => {
clearInterval(interval);
});
}
}
Why DestroyRef is Awesome
graph TD A["Create Resource"] --> B["Register Cleanup"] B --> C["Component Dies"] C --> D["Cleanup Runs Auto!"] style A fill:#e3f2fd style B fill:#fff8e1 style C fill:#ffebee style D fill:#e8f5e9
- Keeps related code together - setup and cleanup side by side
- Works in services - not just components
- Works in functions - pass DestroyRef anywhere
- Multiple cleanups - register as many as you need
🛠️ Real-World Examples
Example 1: Auto-Scroll Chat
export class ChatComponent {
private destroyRef = inject(DestroyRef);
constructor() {
afterRender(() => {
// Scroll to bottom after new messages
const chat = document.getElementById('chat');
chat?.scrollTo(0, chat.scrollHeight);
}, { phase: AfterRenderPhase.Read });
}
}
Example 2: Resize Observer
export class ResponsiveComponent {
private destroyRef = inject(DestroyRef);
constructor() {
afterNextRender(() => {
const observer = new ResizeObserver(() => {
console.log('Size changed!');
});
observer.observe(document.body);
this.destroyRef.onDestroy(() => {
observer.disconnect();
});
});
}
}
Example 3: WebSocket Connection
export class LiveDataService {
private destroyRef = inject(DestroyRef);
connect() {
const ws = new WebSocket('wss://api.example.com');
ws.onmessage = (event) => {
console.log('Data:', event.data);
};
this.destroyRef.onDestroy(() => {
ws.close();
console.log('Connection closed');
});
}
}
🎓 Quick Summary
| Feature | Purpose | When to Use |
|---|---|---|
afterRender |
Run code after EVERY render | Continuous DOM updates |
afterNextRender |
Run code after FIRST render | One-time setup |
earlyRead phase |
Read DOM first | Measurements |
write phase |
Modify DOM | Changes |
read phase |
Read DOM last | Final checks |
DestroyRef |
Register cleanup anywhere | Resource management |
đź’ˇ Pro Tips
- Prefer
afterNextRenderfor initialization - it’s more efficient - Separate reads and writes to avoid layout thrashing
- Register cleanup immediately after creating resources
- Use DestroyRef in services - not just components
- Inject DestroyRef - don’t create it manually
🚀 You’re Ready!
You now understand Angular’s Modern Lifecycle!
- afterRender hooks = Know when the screen updates
- DestroyRef = Clean up like a pro
Go build something amazing! 🎉
