All You Wanted to Know About JavaScript’s Event Loop, Browser Rendering Pipeline, Frames, and Angular (But Were Afraid to Ask)
Ever wondered what really happens between a JS event and the browser render in just 16ms? This guide breaks it down step-by-step with Angular in mind.
⏱ Estimated Reading Time: 5 minutes
📋 WHAT THIS ARTICLE COVERS
We’ll break down:
All of this happens inside a 16ms frame.
⏱ THE 16MS FRAME BUDGET
Browsers target 60 FPS, which means one frame must complete in about 16.67ms.
What needs to happen within that frame:
If any step takes too long, the frame is dropped, and the UI appears to stutter.
🔄 EVENT LOOP SIMPLIFIED
The JavaScript event loop enables asynchronous behavior in a single-threaded environment.
Core components:
How the loop works:
Where Angular CD Happens:
So yes — Angular’s change detection happens inside the call stack.
⚡ MICRO VS MACROTASKS
Microtasks run sooner.
console.log('start');
setTimeout(() => console.log('macrotask'), 0);
Promise.resolve().then(() => console.log('microtask'));
console.log('end');
Output: start → end → microtask → macrotask
Why? Because microtasks run right after the current script, before any new macrotasks.
🎨 HOW THE BROWSER RENDERS A FRAME
Once JavaScript finishes, the browser may do:
Each stage consumes part of the 16ms budget.
🕸️ HOW ANGULAR KNOWS TO UPDATE (ZONE.JS)
Angular uses Zone.js to wrap async tasks.
Zone.js patches browser APIs (e.g., setTimeout, addEventListener). When a task finishes, it tells Angular, which runs change detection to update the DOM.
That’s why you don’t manually refresh the UI – Angular watches async operations automatically.
Recommended by LinkedIn
🔁 LIFECYCLE HOOKS VS BROWSER RENDERING
Angular runs hooks before the browser renders the frame.
Typical sequence:
🛡 SAFE DOM ACCESS
Never access DOM in ngOnInit. It's too early.
DOM elements (e.g., @ViewChild) are only available after ngAfterViewInit. If you access them earlier, they might be undefined.
For even safer timing, use NgZone.onStable + requestAnimationFrame.
⏳ DOM TIMING: onSTABLE + rAF
Sometimes you need to perform a DOM action (like .focus() or measuring size) after Angular has finished change detection and after the browser has painted. This triple-timing pattern helps you land in that exact sweet spot:
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
requestAnimationFrame(() => {
setTimeout(() => {
this.input.nativeElement.focus();
});
});
});
This ensures the focus action happens in the next frame, after Angular is done and the DOM is fully painted.
✅ Step-by-step breakdown:
1. ngZone.onStable.pipe(take(1))
2. requestAnimationFrame(...)
3. setTimeout(...)
✅ Why this is the safest timing
This pattern is ideal for:
You wait for Angular with onStable, align with the next frame using requestAnimationFrame, and ensure the paint is complete using setTimeout. That’s why this works — you land in the first calm moment after everything settles.
🏃♂️ PERFORMANCE: runOutsideAngular()
For high-frequency tasks (e.g., scroll, mousemove, polling):
Use runOutsideAngular() to avoid triggering change detection.
this.ngZone.runOutsideAngular(() => {
setInterval(() => {
// this won’t trigger CD
}, 1000);
});
Only re-enter Angular (this.ngZone.run(...)) if needed.
🕒 FULL TIMELINE: ONE FRAME
Here’s what happens inside a single frame:
All of that must finish before the 16ms deadline.
🗝 KEY TAKEAWAYS
Sales Operations Coordinator and Business Leadership specialist at @ Vidisco Ltd. and Vidisco Usa inc.
1moAmazing. Reposting !