8

I have a hostListner listening to the scroll event in my Angular2 application. I'm using it to check if the user scrolls at the bottom of the page and execute a method when the user reaches the bottom of the page. I have constructed the hostlistner in the following way:

  @HostListener('window:scroll', [])
  onWindowScroll() {
   const scrolledPercent = /* some logic to calculate the percentage scrolled */
  if ( condition1 && condition2 .... )
    { 
     // doing something here
    }
  }

But this is slowing down my application, not in a very significant way but the scrolling on the page is not smooth anymore. Maybe because the hostlistner is constantly looking for the page to scroll, so that subscription is making the whole scrolling experience laggy. I tried by removing the hostListner and the scroll was smooth again. Has anybody noticed this behavior? If not, what is the best way to subscribe to the scroll event on a page using Angular2?

Aiguo
  • 3,416
  • 7
  • 27
  • 52

1 Answers1

17

You can run this function outside angular zone to prevent redundant change detection cycles.

For that i would override EventManager to keep listener outside zone.

custom-event-manager.ts

import { Injectable, Inject, NgZone  } from '@angular/core';
import { EVENT_MANAGER_PLUGINS, EventManager } from '@angular/platform-browser';

@Injectable()
export class CustomEventManager extends EventManager {
  constructor(@Inject(EVENT_MANAGER_PLUGINS) plugins: any[], private zone: NgZone) {
    super(plugins, zone); 
  }    

  addGlobalEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
    if(eventName.endsWith('out-zone')) {
      eventName = eventName.split('.')[0];
      return this.zone.runOutsideAngular(() => 
          super.addGlobalEventListener(element, eventName, handler));
    } 

    return super.addGlobalEventListener(element, eventName, handler);
  }
}

app.module.ts

  ...
  providers: [
    { provide: EventManager, useClass: CustomEventManager }
  ]
})
export class AppModule {}

And update view only by calling changeDetector.detectChanges

@HostListener('window:scroll.out-zone', []) // notice out-zone
onWindowScroll() {
  const scrolledPercent = /* some logic to calculate the percentage scrolled   */
  if ( condition1 && condition2 .... ) { 
      this.cd.detectChanges();
  }
}

Plunker Example

See also

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 1
    This doesn't work with Angular 4 unfortunately. I get this TS build error because the definition of `addGlobalEventListener` changed. – Dai Feb 28 '18 at 07:47
  • To get this working in Angular 4+ update input property `element: HTMLElement` to `target: string` or as defined in https://angular.io/api/platform-browser/EventManager – Richard Medeiros Apr 20 '18 at 14:47
  • 1
    This is extremely clever, great answer. A problem though with overriding/extending Angular services/directives is that every time the framework is updated, you have to remember to check the source code of every extended class to see that the internals have not changed! Easy to introduce bugs this way – Drenai Jul 03 '18 at 14:32