Simple Angular context help component or how global event listener can affect your perfomance

Alexey Zuev
Angular In Depth
Published in
3 min readNov 13, 2020

Imagine we need to create reasable context help component that can be easily added as an attribute to any DOM element like:

Under the hood this component should add a help icon at the end of the wrapped content. Once user clicks on this icon some help dialog should appear. The dialog should be closed once user clicks outside or hits Escape button. That’s all.

We don’t want to use any additional library but only plain Angular code. So, let’s do that.

To achieve this functionality, we’ll be using <ng-content> in order to preserve wrapped content. Also, we will create additional container that will contain icon and dialog itself.

Here’s complete template:

context-help.component.html

Decorators are heavily used in Angular world so we tend to use them everywhere. We’ll be utilizing @HostListener for our purpose. Noting hard should be here and I bet you already did something similar:

Everything should work seamlessly unless we face some performance issue.

What can be the problem here?

Maybe we won’t face this issue if we have only several instantiated ContextHelp components. But in real case we put 100 and more such attributes on a page.

First thing that I usually do when profiling Angular application is put logpoint inside ApplicationRef.tick() method:

Now, I want to remind you that each event listener that was registered within Angular Zone will trigger change detection in Angular tree view.

What we have right now?

We utilized two HostListeners that are listening to global events. Each time user clicks on document or hits Escape button Angular will trigger change detection. The more instances of ContextHelpComponent we have the more change detection cycles Angular will execute.

Here’s what happens if we have added 100 context-help attributes on a page.

I hope you utilize onPush change detection in many parts of your application otherwise you will be in trouble 😉

Solutions 🎉

In order to remedy this behavior and reduce change detection cycles we can think of many options:

  • coalescing events feature

With this feature enabled

Angular will defer change detection cycle by using requestAnimationFrame. As a result we will have only one tick .

You can read more about this feature in this great article by Netanel Basal https://netbasal.com/reduce-change-detection-cycles-with-event-coalescing-in-angular-c4037199859f)

  • reusable clickOutside directive which utilizes the same pair of @HostListener s

This way we defer subscription to those events until dialog appears in the DOM.

  • remove @HostListeners from ts code and move subscriptions to template by leveraging (output) events on dialog:

This behave similar to previous option. Angular won’t subscribe to global listener until dialog appears in the DOM.

Thank you for reading! Keep coding!

--

--