The What, When, and Why

Microtask Queue + RxJS + Angular — figure 1

Working with Angular often means dealing with sophisticated internal systems that are not immediately obvious. Gaining insight into these mechanisms can lead to noticeable improvements in the efficiency of your application.

Let's explore the relationship between the Microtask Queue, RxJS, and Angular's change detection system.

Image source: github.com/TheAlgorithms/JavaScript

Microtask vs. Macrotask

Before going deeper, it’s important to understand the difference between microtasks and macrotasks. In asynchronous contexts, Observables from RxJS function as microtasks.

This characteristic allows them to run before macrotasks such as those from setTimeout. What makes this distinction significant?

Microtask Queue + RxJS + Angular — figure 3

RxJS and Asynchronicity

In RxJS, values emitted by Observables during asynchronous operations are handled subsequent to the resolution of promises. This ordering affects the sequence of execution, which is particularly relevant in managing asynchronous workflows.

Microtask Queue + RxJS + Angular — figure 4

Angular’s Change Detection

Angular’s change detection is structured to execute once the microtask queue has been drained.

This approach guarantees that the UI reflects changes only after all microtasks, which include Observables, have been completed.

This is a delicate yet effective architectural decision that helps maintain UI consistency.

The Role of Zone.js

Zone.js has a crucial function. It keeps track of asynchronous activities and makes sure Angular’s change detection runs after microtasks have finished.

RootZone (outerZone)

At its core, Zone.js operates on the root zone. This top-level zone provides the fundamental capabilities needed to monitor asynchronous operations.

Forking and Hierarchical Structure

Zone.js establishes a hierarchical structure for zones. This implies that new zones are formed by "forking" an already existing one. When forking, the new zone adopts the characteristics of its predecessor, while potentially adding new features or overrides.

This hierarchy creates a layered system where child zones can leverage the monitoring from their parent zones while also adding their own specific behaviors.

NgZone (innerZone)

Angular does not operate directly on the root zone. Instead, Angular creates its own custom zone by forking the root zone.

This forked zone, known as NgZone (also called the "inner zone"), is enhanced with Angular-specific characteristics. Angular operates within NgZone to monitor changes and determine the timing for change detection.

The forking capability and the resulting hierarchy offer significant flexibility. It enables modular and multi-layered tracking of async tasks.

For example, while the root zone handles general monitoring, child zones like NgZone can implement application-specific logic without disrupting the wider monitoring system.

Within Angular, NgZone enables the framework to effectively monitor state changes and refresh the UI, leveraging the core features of the root zone.

Microtask Queue + RxJS + Angular — figure 5

A Typical Sequence

Consider the following typical flow of operations:

  1. The user performs an action, such as clicking a button.

  2. This action fires an event.

  3. This event initiates an API request.

  4. Promises and Observables (in async contexts) are added to the Microtask Queue.

  5. Callback functions from setTimeout are placed in the Macrotask Queue.

  6. Promises are resolved first, as they are at the front of the Microtask Queue.

  7. Observables subsequently emit their values.

  8. Data is manipulated with RxJS operators.

  9. Angular evaluates the data for any changes.

  10. The user interface updates to reflect the new data.

  11. Macrotasks, like those from setTimeout, are executed last.

Not that hard huh?

The coordination between the Microtask Queue, RxJS, and Angular is a sophisticated internal operation.

Having a grasp of this process is quite valuable. It assists with performance tuning and helps in identifying and fixing potential race conditions.

These finer points are often what separate a functional application from an outstanding one.