How to use Publish-Subscribe Pattern with JavaScript

Written by fivan | Published 2020/06/19
Tech Story Tags: web-design | design-patterns | html | css | javascript | web-development | coding | programming | web-monetization

TLDR Every time you click on a button a square is added and a message with the number of squares is displayed. The code tells you more than a thousand words about the Publish/Subscribe pattern. Here I'm using a Publish-Subscribe Implementation (Module) which is in charge of executing every function that has been subscribed to the event. The only work the handler will do is increment theby one and publish theevent. Here you'll find a really good explanation about The Observer Pattern with JavaScript.via the TL;DR App

The result of the next pen shows the case where I'll use the Publish/Subscribe pattern. Every time you click on the
Event
button a square is added and a message with the number of squares is displayed.
HTML
<button onclick="btnEvent()">Event</button>

<div class="alert border">
  This is the alert
</div>

<div class="squares border">
</div>
As part of the layout I have a button that will trigger a
btnEvent
every time is clicked. You also can see
alert
and
squares
div
containers that are used to display the message with the number of squares and the squares respectively.
CSS
.squares {
  border: solid 1px black;
  max-width: 300px;
  min-height: 50px;
  margin-top: 20px;
}

.square {
  border: solid 1px black;
  width: 150px;
  height: 50px;
  margin: 20px auto;
}

.alert  {
  text-align:center;
  max-width: 300px;
  min-height: 20px;
  margin-top: 20px;
}
The stylesheet contains the classes to style the
.squares 
and
.alert 
containers; and the
.square
that is added every time the button is clicked.
JavaScript
let numberOfSquares = 0;
function btnEvent(){
  numberOfSquares += 1;
  
  // display the squares
  let squares = '';
  Array(numberOfSquares).fill(null).forEach(() => {
    squares += '<div class="square"></div>';
  });
  document.querySelector('.squares').innerHTML = squares;
  
  // display alert message
  const alert = document.querySelector('.alert');
  alert.innerHTML = `The number of squares is: ${numberOfSquares}`;
  
}
The script is composed of the global variable
numberOfSquares
that stores the number of squares that have been added; and the
btnEvent 
handler wich increments the
numberOfSquares
by one and displays the message and squares based on
numberOfSquares
variable.
Using Publish/Subscribe Pattern
Now that we have the use case, I'll refactor the code in the script:
let numberOfSquares = 0;
function btnEvent(){
  numberOfSquares += 1;
  pubsub.publish('addSquare', { numberOfSquares });
}
This is how the
btnEvent 
handler will look like at the end of the day. As you can see the only work the handler will do is increment the
numberOfSquares
by one and publish the
'addSquare'
event. Here I'm using a Publish/Subscribe Implementation (
pubsub
module) which is in charge of executing every function that has been subscribed to the
'addSquare'
event.
The object that is passed as a parameter in the
pubsub.publish
method (
{ numberOfSquares }
) will be accessible to all the functions.
Let's add the rest of the code to understand what I'm talking about.
Adding Publish/Subscribe Implementation
const pubsub = (() => {
  const events = {};

  let subscribersId = -1;

  function publish(event, data) {
    if (!events[event]) {
      return false;
    }

    const subscribers = events[event];
    subscribers.forEach((subscriber) => {
      subscriber.func(event, data);
    });
    return true;
  }

  function subscribe(event, func) {
    if (!events[event]) {
      events[event] = [];
    }

    subscribersId += 1;
    const token = subscribersId.toString();
    events[event].push({
      token,
      func,
    });
    return token;
  }

  function unsubscribe(token) {
    const found = Object.keys(events).some((event) => events[event].some((subscriber, index) => {
      const areEqual = subscriber.token === token.toString();
      if (areEqual) {
        events[event].splice(index, 1);
      }
      return areEqual;
    }));

    return found ? token : null;
  }

  return {
    publish,
    subscribe,
    unsubscribe,
  };
})();
I adapted this implementation from the one made by Addy Osmani at https://addyosmani.com/resources/essentialjsdesignpatterns/book. Here you'll find a really good explanation about The Observer Pattern wich Publish/Subscribe Pattern is derived from, check it out if you want to dig deeper into it.
Adding subscribers
function displaySquares(_event, data) {
  let squares = '';
  Array(data.numberOfSquares).fill(null).forEach(() => {
    squares += '<div class="square"></div>';
  });
  document.querySelector('.squares').innerHTML = squares;
}

function displayAlert(_event, data){
  const alert = document.querySelector('.alert');
  alert.innerHTML = `The number of squares is: ${data.numberOfSquares}`;
}
In our case, the work made by
btnEvent
handler could be split out. We can see
displaySquares
and
displayAlert
functions as subscribers.
Subscribing
displaySquares 
and
displayAlert 
functions
let displayAlertSubscription = pubsub.subscribe('addSquare', displayAlert);
let displaySquaresSubscription = pubsub.subscribe('addSquare', displaySquares);
Here I'm creating the
'addSquare'
event and subscribing
displaySquares
and
displayAlert
functions at the same time.
This is the result:
Subscribe and Unsubscribe Dynamically
What I'd like to do now is subscribe and unsubscribe
displaySquares
function implementing two buttons. I'll add the buttons to our layout:
<button onclick="subDisplaySquares()">Subscribe displaySquares</button>
<button onclick="unsubDisplaySquares()">Unsubscribe displaySquares</button>
The next step is to add
subDisplaySquares
and
unsubDisplaySquares
handlers in our script:
function subDisplaySquares() {
  if(!displaySquaresSubscription) {
    displaySquaresSubscription = pubsub.subscribe('addSquare', displaySquares);
  }
}
function unsubDisplaySquares() {
  if(displaySquaresSubscription) {
    pubsub.unsubscribe(displaySquaresSubscription);
    displaySquaresSubscription = null;
  }
}
You can see the final result here:
And that's it. :)
Featured Image: Man holding his head while sitting on chair near computer desk by Jason Strull

Written by fivan | The code tells you more than a thousand words
Published by HackerNoon on 2020/06/19