Angular Deep dive — Zone.js — How does it monkey patches various APIs
To make this article any sense, one should know what is Zone.js and how does it work. This article will not discuss any details of that. For this, there are separate articles on the web. One can read here and here. You can also read a primer on the Zone.js for detailed information.
However, to give little perspective, I would like to provide some overview.
What is Zone.js or Zone?
A Zone is an execution context that persists across async tasks which allows the creator of the zone to observe and control execution of the code within the zone. To give an oversimplified example, Angular uses Zone.js to create a Zone or execution contexts which is used to run Angular change detection cycle.
In Angular, When we make a call to platformBrowserDynamic().bootstrapModule(AppModule) in our main.ts, module is instantiated. But before actual instantiation, an instance of NGZone is created.
Now, in constructor of NGZone, Zone.js is loaded which creates a root zone. Further, a new child zone named ‘angular’ is created by calling zone.fork method. Here, angular defines all the callbacks as part of zone specifications which must be called whenever any async task is invoked in angular application.
In these callbacks, angular emit events. Further, angular subscribes to these events and runs change detection logic by calling method tick().
Now, the job of invoking callbacks specified in the zone specification lies on the Zone.js. To do this job, Zone.js monkey patches all the async APIs.
What is Monkey patching?
By definition, Monkey patching is basically extending or modifying the original API. Now, zone.js re-defines all the async APIs like browser apis which includes set/clearTimeOut, set/clearInterval, alert, XHR apis etc.
Now, whenever we call any api like below in our angular application,
Instead of calling actual API, JS VM calls the re-defined version of this API which in turn invokes the call backs of ‘angular’ zone and hence helps in change detection.
How does Zone.js monkey patch APIs?
Let’s have a look on the source code of Zone.js.
In build file, we can see that Zone.js is built by compiling rollup-main.ts.
If we see rollup-main.ts, it basically imports 4 files.
In zone import, all the core zone functions/variables are defined. In all other imports, definition of various monkey-patched APIs is given. Now, to monkey patch any API, Zone.__load_patch() function is called with 2 arguments.
First argument is the identifier of the monkey-patched APIs and second is the patch function which when executed, replaces the actual browser API with the re-defined version. Following is the snippet of __load_patch function.
Now, monkey patching logic depends on the API which is being patched. There are various easy APIs which we can study like alert. Further, there are very complex APIs like XHR. For the sake of this article, lets pick up the medium complexity API i.e. set/clearTimeOut.
Monkey-patching set/clearTimeOut API
Now, patch function of all timer apis is defined in browser.ts
For patching set/clearTimeOut,
patchTimer(global, set, clear, ‘Timeout’)
In this function, another method patchMethod() of utils.ts is called which is responsible for actual monkey-patching.
In patchMethod(), original api is saved in local variable i.e. delegate and the same is returned which is saved in setNative variable inside timers.ts.
Now, as part of invoking patchMethod(), we are supposed to pass one function (patchFn) as argument which when executed should return the actual monkey patched API.
Now, in case of set/clearTimeOut, we passed a function which is doing nothing but returning a function.
Now, this function definition is executed every single time we call browser setTimeOut API. In this function, we are actually creating a macro task by calling other utils function i.e. scheduleMacroTaskWithCurrentZone. Here,we are passing the scheduling function. This scheduling function is called by Zone.js synchronously which in turn calls actual browser native API.
Further, in the scheduling function itself, we are replacing the call back of timer function with the custom timer() function. This function actually calls the task.invoke method which in turn will call the callback of setTimeOut.
Here we do not call the original call back so that Zone js can do so and perform all the tasks like switching the zone back to that of scheduling function zone, calling callbacks of zones etc.