Optimizing Angular Change Detection Triggered by DOM Events

Netanel Basal
Netanel Basal
Published in
4 min readJul 9, 2019

As you may already know, Angular leverages ZoneJS in order to find out when an asynchronous task is completed, and as a result, a change detection cycle should be triggered.

One of the browser APIs that’s monkey patched by ZoneJS is addEventListener. So, for example, when we register an event in our component:

Each time the event is fired, NgZone notifies Angular, which causes a new change detection cycle to run. We can easily see this in action by adding a getter to our component’s template:

Scrolling…

The view will be checked, unless your component change detection strategy is set to onPush or Detach.

Let me show you what’s happening under the hood:

Scrolling…

Angular is subscribed to the onMicrotaskEmpty observable, which is fired when all the tasks are completed and the zone is stable. When this observable emits, Angular calls the tick() method, which runs change detection on each view.

Now imagine how inefficient it will be to run a new change detection cycle each time a frequent event such as scroll is fired (which can easily reach 50 events per second).

Even if the majority of your application components are using onPush, there is a chance that a third party library that you’re using isn’t. Moreover, Angular still needs to perform a redundant views check every time, which can slow things down.

Let’s see three ways we can optimize this behavior:

Disable it Globally

Our first option is to add a global flag, which lets us instruct ZoneJS which events not to patch.

Create a new file named zone-flags.ts and add the following line:

Next, import it in your polyfills.ts file:

Now you’ll see that change detection doesn’t run anymore when the scroll event is triggered. Use this option with caution, as unoptimized third-party libraries may depend on it. Check out the polyfills.ts file for more Zone options you can apply.

Run it Outside Zone

The second option is to run the event outside of the Angular zone. We can do so by obtaining a reference to NgZone via DI, and invoking the event inside the runOutsideAngular callback function:

Running functions via runOutsideAngular allows us to escape Angular’s zone and perform tasks that don’t trigger Angular’s change-detection. We can verify this by logging Zone.current.name and see that the result is not angular.

We can go further. Currently, our code isn’t clean, reusable, or RxJS friendly. Let’s create a custom operator to solve these issues:

A custom operator in RxJS is a function that takes a source observable and returns the same observable, a modified version of it, or a new Observable.

In our case, we take a reference to NgZone and call runOutsieAngular, giving it the observable we want to run outside Angular zone. We also return the original subscription, so our users can unsubscribe from it. Now, we can use it wherever we need to:

If we use Ivy, we can skip the part where we pass a reference to ngZone and obtain a reference to it by using the ɵɵdirectiveInject function:

But we still have to wait for Ivy to be stable. An alternative for a custom operator is a custom scheduler. You can examine one of the implementations here.

The third way is creating a custom event plugin as described in the following article:

🚀 In Case You Missed It

  • Akita: One of the leading state management libraries, used in countless production environments. Whether it’s entities arriving from the server or UI state data, Akita has custom-built stores, powerful tools, and tailor-made plugins, which all help to manage the data and negate the need for massive amounts of boilerplate code.
  • Spectator: A library that runs as an additional layer on top of the Angular testing framework, that saves you from writing a ton of boilerplate. V4 just came out!
  • And of course, Transloco: The Internationalization library Angular 😀

Follow me on Medium or Twitter to read more about Angular, Akita and JS!

--

--

Netanel Basal
Netanel Basal

Written by Netanel Basal

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.

Responses (12)