Performance Optimization with Zone-Flags. How to Debug Zone Flags. Part 3

Table of Contents

Zone can and sometimes should be manually configured, especially when customizing change detection in Angular applications for performance reasons. One of the available approaches is to do it over zone flags. Allowing you to fine-tune your Zone, zone-flags can actually bring about significant performance improvements. Yet at the same time, they may introduce new problems of varying severity and complexity if change detection does not work properly.

Running into issues, even those that are hard to debug, does not mean you should go back to the default change detection configuration. Instead, let’s uncover the most common scenarios, in which zone-flags might cause errors or yield unexpected results, and find the best ways to address them.

If you are looking to learn more about Angular’s change detection mechanism, please refer to the following article: Introduction to Change Detection.


Angular Performance - Full Masterclass

High-Speed Angular applications on any device

Zone flags allow configuring zone.js globally, which is generally a good thing, even though not without certain risks.

1. Global vs. local config. Applications that rely on global state management are by nature push-based, meaning that changes are propagated in the state once they occur. This push-based reactivity puts such apps in a good position by eliminating the need for dirty marking as such. However, at the same time, it is also possible to implement specific local state management and thus fix possible bugs quite easily for most of the flags.

💡 Pro Tip:

Local state reduces the possible debugging scope by limiting it to specific feature or UI components.

2. Third-party libraries. Another risk is that statically configured zone flags may affect third-party libraries that rely on the zone-flagged APIs to detect changes. Although this is evidently a pitfall one should be aware of when configuring zone-flags, it is not an insurmountable one.

Chances are nothing will break if you turn off some of your flags. You will start noticing major regressions in the UI only when most of the flags are disabled. That’s just how zone-flags work, and therefore, there is a good rule of thumb to follow if you want to be always one step ahead, preventing any potential regressions.

💡 Pro Tip:

After you disable a zone flag, go and check both the application and its codebase to ensure your change detection is still triggered as intended.

3. Monkey patching. At that, I’d like to stress one more challenge with zone-flags that you will most probably face at some point. By default, zone.js monkey-patches global APIs as well as a whole array of other asynchronous operations to trigger change detection in an Angular application. Unfortunately, zone flags can mingle with this patching mechanism, making it problematic to understand what changes have been introduced and what impact they have on your app. This may sound like a fairly serious problem but there are certain techniques that allow us to effectively address it.

In the following sections, I suggest taking a closer look at the most effective debugging strategies that will help to get the most out of configuring Zone while eliminating most of the issues associated with it.

How to migrate without breaking anything

All the above risks considered, I suggest taking an incremental approach to configuring Zone over zone flags. The following checklist will help you ensure, step-by-step, that your migration is seamless and safe.

1) Make sure ChangeDetectionStrategy.OnPush is set for the application's root component. The purpose of this step is to achieve immutable change propagation, which refers us to risk number one on the above list.

2) Prioritize flags based on the criteria like the location and frequency of their usage or the resulting issues’ complexity.

3) Pick one easy-to-control API you want to flag. Of course, the safest way is to work with one API at a time. And if you want to secure your app from breaking, your surest bet will be to go with these flags:

  • HTTP requests (XHR flags.) The HTTP layer needs to be encapsulated and not used in components, which makes it easy to flag.

  • setTimeout and setInterval (timer flags.) They all are easy to find in a codebase, measure impact, and check for breakages.

  • Mouse/input/keyboard events. Every mouse event has an explicit handler in the code.

  • requestAnimationFrame. There are very few of those in the codebase, so it will be easier to keep track of them.

4) Examine the application codebase for the APIs being used to find the spots where it may break.

5) Identify third-party libraries that employ the flagged API as there is a risk of them breaking.

6) Switch on the zone flag in question.

7) Take flame chart measurements or use breakpoints to ensure they are working. Later in the article, we will take time to figure out how to use breakpoints for debugging purposes.

8) Test your application to see if everything works fine.

How to debug zone.js properly

On top of the discussed migration best practices, there are also several approaches to debugging the zone.js behavior. Namely, it can be fixed over:

  • The browser console

  • The DevTools performance tab

  • Breakpoints

But before we go into detail about how each of these options works in real life, please consider the minimum setup we will need to ensure to go on with debugging. First, we have to add this code into the app.component.ts file:

  selector: 'app-component',
  template: '<div (click)="onClick()">click me</div>',
export class Component {
  onClick(): void {
    setTimeout(() => {
    }, 0);


Then, in polyfills.ts, we need to include the following imports:

import './zone-flags';
import 'zone.js/dist/zone';

The zone flag we will use in our example is the timers flag. Accordingly, in a zone-flags.ts file, you should place the following content:

window.__Zone_disable_timers = true;

⚠️ Notice:

Zone.js not only monkey patches existing APIs but also can add additional properties to the globalThis object, which is represented by window in a browser. While doing so, zone.js maintains both the flags we set on the globalThis object and unpatched APIs over globalThis.

Now that everything is set up, let’s discuss each of our debugging techniques one by one.

1. The browser console

The browser console can log all sorts of front-end activities associated with a specific web page, thus proving instrumental in detecting errors, warnings, and bugs. Working with the console, one can unlock truly advanced logging and debugging capabilities and access vital information varying from network requests and responses to security warnings to complete details on specific DOM elements.

In particular, all these features make the browser console a perfect tool for debugging zone.js. With this in mind, let’s consider an example of troubleshooting and resolving patching issues using the browser console.

For the sake of example, let’s consider what happens before and after timer APIs are patched and how the console can come in handy at these two stages.

But first I’d like to stress that, generally speaking, to figure out whether our zone flag is enabled or not, we can log it in the browser. If Zone is patching the API, it will always restore the original API under global variables (here: __zone_symbol__setTimeout, __zone_symbol__clearTimeout) where these very methods are stored. Otherwise, if you have the flag on and try to log the same global variables, you will get undefined because there is no original method stored in there.


Now, let’s walk through the two states in a bit more detail.

Before timer APIs are patched

First off, you can check if the flag is disabled by accessing the window object directly and getting the logging information displayed on the console:

// logs 'undefined' if the flag is active


Another thing you can check using the browser console is whether or not Zone has actually patched the respective API. To this end, you should search for the original version of the API, which is supposed to be stored at the globalThis object if zone.js has applied the patch. If zone.js has not done it, the original API won’t be there either.

// logs the unpatched/original setTimeout API if zone was active

This is the result you will get if you try to access the logging information before timer APIs are patched:


After timer APIs are patched

Correspondingly, we will get the opposite results if we run the above two checks after using a zone flag for timers.

Now, if we try to verify whether the flag is set, the function should return true:

// logs 'true' if the flag is active

And if we run the second check after APIs are patched, we should get the logs of the original unpatched setTimeout API:

// logs 'undefined' if zone was not active

Here is the output you will see if APIs are unpatched:


💡 Pro Tip:

RxAngular offers more convenient ways to configure and debug Zone-Flags. First, add the following content into zone-flags.ts:

import { zoneConfig } from '@rx-angular/cdk';;

After zoneConfig is imported, check zone-flags with RxAngular debug helper window.__rxa_zone_config__log() in the browser console:

window.__rxa_zone_config__log(); // logs all active flags

2. The DevTools performance tab

Chrome DevTools are known for their comprehensive performance tuning and debugging features. This is why, in our examples, we will be using the DevTools’ performance tab to address such issues:


Also, we will be referring to Flame Charts, for they can show us the state of the JavaScript code that is being executed. This transparency of the data visualized in the flame charts not only allows for easier debugging of our zone-flags but generally makes Flame Charts a necessity for performance profiling and debugging.

The following example shows how the flame charts for patched and unpatched timer APIs differ. 

Before timer APIs are patched

By default, zone.js monkey patches timer APIs via methods like setTimeout() to make async callbacks of those APIs in the same zone when it is scheduled. Among others, this means the setTimeout() callback will be invoked in the same zone as an output.

Consequently, in the recording of a flame chart below, you can see a whole bunch of scheduled tasks piling up from a single setTimeout.


After timer APIs are patched

Alternatively, if you set __Zone_disable_timer to true before importing zone.js, Zone will not monkey patch the timer APIs, it will not get triggered altogether, and as a result no tasks will get scheduled.


__zone_symbol__UNPATCHED_EVENTS = ['click'];


__Zone_disable_timer = true;


💡 Pro Tip:

Recordings should capture fully identical interactions with flags on/off to ensure a valid comparison. By fully identical, we should understand that even the mouse moves and time to wait in between the interactions are crucial. Keeping everything exactly the same will make finding the right spot in flames easy and fast.

You can use this tool to compare two charts by dragging and dropping the profile measurements into the UI. Also, remember to download a report once it’s ready as a JSON file.

Every .js file has its own different color on the chart, which is assigned randomly except for native browser calls – they are yellow. It means that your report can be filtered according to your individual needs. So, for example, to see who invoked Timer (a long curve on the flame chart), tick the corresponding checkbox in extended preferences:


Then, search for 'tick' – it indicates that ApplicationRef#tick was most probably invoked by zone.js (ctrl+F to search over the flame chart):


⚠️ Notice:

Some events may be caused by your browser extensions. To avoid that, open the tab in incognito mode.


Zone.js patching mechanism is also visible in the flame charts’ timing line. You can find the timing marks before the bootstrap phase of Angular.


Every patched API is listed there as well.


💡 Pro Tip:

Unpatched APIs should not show up in the timing line. This can serve as another check. If you can't see the timings panel, check out this link.

3. Breakpoints

Breakpoints should be one of the main debugging techniques in your toolbox in general and especially when configuring zone-flags. You can set a breakpoint on any line of executable code to pause the code execution there and inspect your program’s current state. 

Below, you can see Zone Patches listed in a flame chart:


In order to use breakpoints for debugging purposes here, you should navigate to zone.js code and check if the patch is invoked.

As you can see, there are quite a number of measures and precautions that you can take to ensure your zone flags’ successful configuration. Thus, taking into account all the possible risks and having the right debugging strategies in place – in particular, those using the browser console, DevTools performance tab, Flame Charts, and breakpoints – you can prevent the issues that might occur when configuring zone.js or debug your code when it doesn’t work properly.

If you wonder how Zone Flags work in RxAngular, feel free to check out the previous article: A Close Look at Zone Flags in RxAngular.