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

Post Editor

Imagine we need to create reusable context help component that can be easily added as an attribute to any DOM element.

3 min read
post

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

Imagine we need to create reusable context help component that can be easily added as an attribute to any DOM element.

post
post
3 min read

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

<>Copy
<h3 context-help="Some description 1">Some title 1</h3>

Under the hood, this component should add a help icon at the end of the wrapped content. Once the user clicks on this icon some help dialog should appear. The dialog should be closed once the user clicks outside or hits the 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 an additional container that will contain the icon and dialog itself.

Here’s the complete template:

<>Copy
<ng-content></ng-content> <div class="context-help-container" #container> <i (click)="showHelp = true;"></i> <div *ngIf="showHelp" class="context-help-dialog"> {{ content }} </div> </div>

Decorators are heavily used in the 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:

<>Copy
import { Component, OnInit, Input, ElementRef, HostListener, ViewChild } from '@angular/core'; @Component({ selector: '[context-help]', templateUrl: './context-help.component.html', styleUrls: ['./context-help.component.css'], }) export class ContextHelpComponent { @Input('context-help') content: string; @ViewChild('container') containerRef: ElementRef; showHelp = false; @HostListener('document:click', ['$event']) documentClicked({ target }: MouseEvent) { if (!this.containerRef.nativeElement.contains(target)) { this.showHelp = false; } } @HostListener('window:keydown.Escape') escapedClicked(): void { this.showHelp = false; } }

Everything should work seamlessly unless we face some performance issue.

Сheck this link.

What can be the problem here?
Link to this section

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

The first thing that I usually do when profiling Angular application is put log point 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 the 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
Link to this section

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

  • coalescing events feature

With this feature enabled

<>Copy
platformBrowser() .bootstrapModule(AppModule, { ngZoneEventCoalescing: true });

Angular will defer the 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.

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

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

  • remove @HostListeners from ts code and move subscriptions to the template by leveraging (output) events on the dialog:
<>Copy
<div *ngIf="showHelp" class="context-help-dialog" (document:click)="documentClicked($event)" (window:keydown.Escape)="escapedClicked()" > {{ content }} </div>

This behaves similarly to the previous option. Angular won’t subscribe to global listeners until a dialog appears in the DOM.

Check this link.

Thank you for reading! Keep coding!

Share

About the author

author_image

Alexey is a GDE for Angular and Web Technologies and also active StackOverflow contributor.

author_image

About the author

Alexey Zuev

Alexey is a GDE for Angular and Web Technologies and also active StackOverflow contributor.

About the author

author_image

Alexey is a GDE for Angular and Web Technologies and also active StackOverflow contributor.

Looking for a JS job?
Job logo
PDQ team| Senior JavaScript developer (Angular/Node)

SD Solutions

Ukraine
Remote
$60k - $80k
Job logo
Senior Full-Stack Developer (Node+Angular)

A-Listware

Ukraine
Remote
$48k - $78k
Job logo
Senior Full stack (Angular+Node)

Monolith

Ukraine
Remote
$60k - $84k
Job logo
AngularJS Developer/.net Core - Remote Contract

InfoMagnus

United States
Remote
$115k - $134k
More jobs
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
AngularpostTracking user interaction area

13 September 2021

8 min read

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
Angularpost
7 September 202122 min read
Designing Angular architecture - Container-Presentation pattern

Designing architecture could be tricky, especially in the agile world, where requirement changes are frequent. So your design has to support that and provides extendibility without the need for serious modification. In such cases, you will find the Container-Presentation pattern instrumental.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more
micro frontendspostTaking micro-frontends to the next level

6 September 2021

25 min read

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more