How event listener can affect your performance

    event listner

    Angular has some extraordinary features that could ease your complexities and provide a smooth user experience. Having support for special type of observables such as replay subject, Async subject, Behaviour subject in Angular, you can keep your data updated automatically across the application. In this blog, we will learn about event listeners in Angular and how it affects application performance.

    What is an Event Listener?

    An event listener is a function or procedure that waits for an event to occur in a computer program; This event can be a user clicking or moving the mouse, an internal timer, or pressing a key on the keyboard. The listener is essentially a loop programmed to react to an input or signal.

    • We need to create a reusable context component that can be added as an attribute to any document object model element like:

    <h3 context-help = ?Description 1?> Some Title of the Heading </h3>

    This component should add a help icon to the end of the wrapped content. When the user clicks this icon, a help dialog should appear. The dialog box should close when the user clicks outside or presses the Escape button.

    • We don’t want to use additional libraries; we just want to use simple Angular code. So let’s do this. We will be using <ng-content> to preserve wrapped content to achieve this functionality.

    <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 used a lot in the Angular world, so we tend to use them everywhere. We will use @hostListener for our purposes.

    import { Component, OnInit, ElementRef, HostListener, Input, ViewChild } from ‘@angular/core’;

    @Component({

      selector: ‘app-root’,

      templateUrl: ‘./app.component.html’,

      styleUrls: [‘./app.component.scss’]

    })

    export class AppComponent {

      title = ‘EventListenerDemo’;

      @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 run smoothly unless you run into a performance issue. We might not run into this problem if we only have more than one component built-in. But in the real case, we put 100 or more of these properties on a page.

    We use two HostListeners listening to global events. Angular will trigger change detection every time the user clicks on the document or hits the Escape button. The more instances of the component then have more change detection cycles angular will execute.

    • We can consider several options to correct this behavior and reduce change detection cycles.
    •  Coalescing events feature, with this feature enabled

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

    Using the requestAnimationFrame, Angular will defer the change detection cycle. reusable clickOutside directive using the same @HostListener pair. That way, we’ll defer subscribing to these events until a dialog opens in the DOM.

    • To remove @hostListeners from ts tag and move subscriptions to template using output events from dialog:

    <div *ngIf =”showHelp”

      Class =”context-help-dialog”

      (document:click)= “documentClicked($event)”

      (window:keydown.Escape)= “escapedClicked()”>

      {{ content }}

    </div>

    This behaves the same as the previous option. Angular would not subscribe to global event listeners until a dialog appears in the DOM.

    • The different types of methods and patterns of the EventListener that can be used.

    Direct Binding:

    The oldest and easiest way to listen to document object model events is to register an event handler function directly on the node. Most DOM events are propagated from the original target nodes in the tree.

    Event Delegation:

    Event delegation allows you to avoid adding events to the particular nodes; instead, the event listener is added to a parent. It takes advantage of that most events are “bubbles” in the DOM tree, which means that they can be caught at the root of the tree and processed there.

    Let’s say we have a parent UL element with multiple child elements:

    <ul id =”parent-list”>

      <li id =”id1″> Item 1 </li>

      <li id =”id2″> Item 2 </li>

      <li id =”id3″> Item 3 </li>

      <li id =”id4″> Item 4 </li>

      <li id =”id5″> Item 5 </li>

      <li id =”id6″> Item 6 </li>

    </ul>

    Let’s say we have an unordered list of HTML that we want to bind event handlers to. You can add a separate event listener to each LI element but list items are frequently added and removed from the list. Instead of binding a Click event handler for each list item, add the event listener to the parent UL element. When the event reaches UL, it checks the target property of the event object for a reference to the actual node the user clicked.

      // Get the element, add a click listener…

    <ParentDOM>.addEventListener(“click”,function(e) {

      // e.target was the clicked element

      if (e.target && e.target.matches(“a.classA”)) {

        console.log( “Anchor element clicked!”);

      }

    });

    • There are two types of event delegation:

    [ 1 ] Component Delegation: A listener is registered for each event for the component on the page.

    [ 2 ] Front Controller Delegation: A listener is registered for each event for all the components, and then attributes in HTML are used as a dynamic reference to functions or objects that represent each component.

    Debounce:

    There are some situations where you want a function bound to an event to fire only once after a specific period of time. Typical candidates are functions related to resizing and scrolling events. Calling a function each time one of these events fires will have a significant impact on performance.

    RequestAnimationFrame:

    If you have to need to perform animating in Javascript, then you have probably used setTimeout or setInterval to achieve it.

    function myFunction() {

      // Do whatever

      requestAnimationFrame(myFunction);

    }

    requestAnimationFrame(myFunction);

    • The advantages of the requestAnimationFrame are:

    They are optimized for the browser, so they can run at 60fps in most cases. Battery life and page responsiveness are better on low-end devices and mobile browsers, because of the performance.

    Scroll events with Passive Event Listeners:

    It allows developers to better scrolling performance by eliminating the need to scroll to block on wheel and touch event listeners.

    document.addEventListener( ‘wheel’, (evt) => {

      // … do stuff with evt

    }, {

      capture: true,

      passive: true

    })

    Conclusion:

    In this blog, we have seen what is events listener and how an event listener affects your performance. We have seen the different types of methods and patterns of the EventListener.

    Author Bio: Vinod Satapara ? Technical Director, iFour Technolab Pvt. Ltd.

    Technocrat and entrepreneur with years of experience building large scale enterprise web, cloud and mobile applications using latest technologies like ASP.NET, CORE, .NET MVC, Angular and Blockchain.

    Hire Angular developers from us to address your business concerns.

    Donna

    As the editor of the blog, She curate insightful content that sparks curiosity and fosters learning. With a passion for storytelling and a keen eye for detail, she strive to bring diverse perspectives and engaging narratives to readers, ensuring every piece informs, inspires, and enriches.

    Leave a Reply

    Your email address will not be published. Required fields are marked *