Feb 15, 2015

4 min


AngularJS WindowEventsService - Centralized Throttled Window Events

Article Purpose

The purpose of this article is to suggest a standardized way for an AngularJS app to listen for throttled window events. The goal behind this approach is to provide a centralized location and a default throttle time that all window events ('scroll', 'resize', 'keyup', 'keypress', 'keydown', etc) utilize. The WindowEventsService encapsulates this functionality, but a throttle time overwrite option is provided too as specific controllers or directives will inevitably need varying throttle times (time of 0 included). If you’re unfamiliar with the throttle concept, Ben Alman has a great post explaining its value.

Past vs. Current Approach

In the past, my JavaScript development relied heavily on jQuery. Over the past six months however, I’ve adopted AngularJS. I’ve done so mainly for its directives concept, built-in routing, and its powerful data binding. Prior to AngularJS, I wrote and used a SignalsManager module that leveraged Signals by Miller Medeiros. This SignalsManager was a great solution for application-wide communication (via the Event Aggregator pattern). With AngularJS this is not needed anymore, as data binding accomplishes 90% of what I need automatically. For the last 10%, I can leverage a $watch on shared service value(s). The SignalsManager approach is superior to AngularJS’s data binding and $watch solutions in one aspect however. SignalsManager provides a centralized location for any application component to tap into throttled window events. The WindowEventsService is my AngularJS specific solution for providing this centralized location for application components to listen for throttled window events.


Below is the actual WindowEventsService definition. In this particular implementation, the service utilizes the underscore library for its throttle method. This library isn’t required, but an implementation of a throttle method is (via another library or manually written).

app.service("WindowEventsService", ['$window', function($window){
  var api = {
    throttleTime: 200,
    listen: listen
  //listener updates
  function listen(isOn, type, method, time) {
    //clean time, determine listener type
    var debounceTime = isNaN(time) ? api.throttleTime : time,
        listenerType = isOn ? 'addEventListener' : 'removeEventListener';
    //update throttled listener
    $window[listenerType](type, _.throttle(method, debounceTime));
  return api;

Now in a controller or directive, you could dependency inject WindowEventsService to add/remove listeners for particual window events (that are automatically throttled) using a single listen() method call. You could also reset the default throttle time at application start (or by simply changing the literal 200 value in the service itself).

//example - change default throttle time at application startup
WindowEventsService.throttleTime = 150;

//example - listeners for window scroll event with onScroll callback
WindowEventsService.listen(true, 'scroll', onScroll); //addEventListener
WindowEventsService.listen(false, 'scroll', onScroll); //removeEventListener

//example - listener for window resize event with onResize callback, override throttle time
WindowEventsService.listen(true, 'resize', onResize, 500); //addEventListener
WindowEventsService.listen(false, 'resize', onResize); //removeEventListener

You can view a working example that uses the WindowEventsService at this codepen sketch. Feel free to expand this service with a debounce() method or modify it to your desire. If you use a different approach entirely, send me a link on Twitter @derekknox as I'm always eager to learn about the different/better approaches of others.