/apr 14, 2015

Developing with the Publish-Subscribe Pattern in JavaScript

By Kyle Clark

Software design patterns provide a means to communicate complex software concepts with a widely understood and agreed upon terminology. At their core, design patterns are a template for creating reusable solutions to commonly occurring software problems. They aren't as much a tool, yet a technique for problem solving what is commonplace to software development. In JavaScript, the Publish-Subscribe design pattern is significant in the architecture of many successful applications, and we will demonstrate how you can utilize it in your development.

Publish-Subscribe is frequently used in front-end code -- even developers who are not familiar with Publish-Subscribe by name will unknowingly use the pattern. This may look familiar.

// Javascript HTML DOM Event Example

document.getElementById('makeItHappenBtn').onclick = makeItHappen;

function makeItHappen() {
    document.getElementById('itsHappening').innerHTML = 'It is happening!';
}

// jQuery Event Handler Example

$('#makeItHappenBtn').on('click', function () {
  document.getElementById('itsHappening').innerHTML = 'It is happening!';
});

What we're stating in our code is on the click of the button run this function. We are subscribing to an event -- the click of the #makeItHappenBtn element -- and the DOM is handling the publish of the event (emitting the click event for our event handler to capture). Simple concept which nearly every JavaScript developer has likely used at one time or another for various purposes.

A key benefit of the Publish-Subscribe pattern is the establishment of single sources of responsibility. It facilitates the creation of an architecture that is easier for developers to maintain and reason about the intentions of the code. For a more elaborate scenario of utilizing the Publish-Subscribe pattern, I've added a large section of code below, so you can see how the Publish-Subscribe pattern may be used for practical purposes.

The example demonstrates how to integrate the design pattern when creating a Tracking component for handling various user events -- in this case a form submission. The Publish-Subscribe technique allows for the tracking code to be abstracted out of the Form class and into its own class handler. The Tracking class subscribes to key events, and the Form class is the example source for publishing an event the Tracking class is concerned about (the user form submission).

Note: the code examples follow a RequireJS code structure for clarity of file dependencies

PubSub.js

/* PubSub.js abstracts the implementation for the publish, subscribe, and unsubscribe pattern methods. */

/* Utilizes jQuery for method implementation but the rest of the code is not dependent on the implementation. So, it may be changed at any time without fear of application-level impact. */

/* To that end, the implementation details of each method can be changed to suit your needs (i.e. pure JavaScript, jQuery, Angular, etc) without needing to make global changes. */

define([
  'jquery'
], function ($) {
  'use strict';

  /**
  * Publish-Subscribe Pattern Implementation
  */

  var
    PubSub = {},
    obj = $({});

  PubSub.publish = function () {
    obj.trigger.apply(obj, arguments);
  };

  PubSub.subscribe = function () {
    obj.on.apply(obj, arguments);
  };

  PubSub.unsubscribe = function () {
    obj.off.apply(obj, arguments);
  };

  return PubSub;
});

EventHub.js

/* EventHub.js is the single storage hub for event notification definitions */

define([
], function () {
  'use strict';

  var EventHub = {};

  EventHub.FormSubmit = 'formSubmitEvent';

  return EventHub;
});

Form.js

/* Form.js is a generic class to publish an event on the form element submission */

define([
  'js/utils/EventHub',
  'js/utils/PubSub'
], function (EventHub, PubSub) {
  'use strict';

  /* Form class ... */

  var Form = {};

  /* insert logic to call onSubmit on form element submission */

  Form.onSubmit = function () {
    PubSub.publish(EventHub.FormSubmit, arguments);
  }

  return Form;
});

Tracking.js

/* Tracking.js is a generic class to subscribe to events for tracking */

define([
  'js/utils/EventHub',
  'js/utils/PubSub'
], function (EventHub, PubSub) {
  'use strict';

  /* Tracking class ... */

  var Tracking = {};

  /* insert logic to call subscriptions for tracking events */

  Tracking.subscriptions = function () {
    PubSub.subscribe(EventHub.FormSubmit, function (ev, args) {
        Tracking.onFormSubmit(ev, args);
      });
    }
  };

  Tracking.onFormSubmit = function () {
    // insert logic for tracking FormSubmit event

    // if desired, unsubscribe from the event so the event subscription will be removed
    PubSub.unsubscribe(EventHub.FormSubmit, arguments);
  };

  return Tracking;
});

Related Posts

By Kyle Clark