How to use Publish-Subscribe Pattern with JavaScript | Hacker Noon

June 19th 2020

Author profile picture

@fivanIvan Guzman

The code tells you more than a thousand words

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,
  };
})();

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

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!

read original article here