Observer Design Pattern in JavaScript

Observer Design Pattern in JavaScript

Everything you need to know about the Observer design pattern in JavaScript

Everything you need to know about the Observer design pattern in JavaScript

While working with any language, we tend to use several reusable design solutions to commonly occurring problems. In JavaScript, too, we have a mix of well-defined patterns.

The Observer pattern is one of them.

In this article, we shall understand more about the Observer design pattern in JavaScript and implement a small example in vanilla JavaScript.


What Is the Observer Design Pattern?

The Observer pattern follows a subscription model. A subscriber (commonly referred to as the observer) subscribes to an event or an action handled by a publisher (commonly referred to as the subject) is notified when the event or action occurs.

The subject broadcasts the occurrence of the event or action to all the observers.

When the observer no longer wishes to be notified of the changes by the subject, it unsubscribes itself from the subject, and the subject then removes it from the list of subscribers.

An Observer design pattern is very similar to a Publisher/Subscriber pattern, with a small difference that a Publisher/Subscriber pattern also specifies a topic it wants to subscribe to. For example, when detecting keyboard shortcuts, the subscriber can choose to specify a key combination that it wants to listen to in a Publisher/Subscriber model.


Implementation of the Observer Pattern

As an example of the Observer pattern, we will go about implementing a simple interaction where multiple elements listen to the mouse position on the screen and perform different actions.

Below is an example of what our interaction looks like:

Demo interaction

Before we implement this interaction, let us analyze what is happening in this example as the mouse position changes.

  • The mouse position is immediately updated in the textbox at the top right corner.

  • The circle follows the trajectory of the mouse after a delay of 1s.

From the above description, we see that multiple components need information about the same thing but behave differently.

From the above example, we identify that the subject listens to the mouse event on the window and relays it to whoever wants it. The circle and textbox are observers in the above example.

So let us now go ahead and implement it.

Step 1. Implement a MousePositionObservable class

As a first step let us go ahead and implement the MousePositionObservable class. This class needs to do the following things:

  • Keep a list of observer callbacks.

  • Expose a subscribe method which the observers will call to subscribe to the change. The return value of this must be a function that will move the callback from the set of subscriptions when called.

  • Listen to mouseMove event and trigger all subscription callbacks.

The code looks like the below:

class MousePositionObservable {
  constructor() {
    this.subscriptions = [];
    window.addEventListener('mousemove',this.handleMouseMove);
  }
  handleMouseMove =  (e) => {
     this.subscriptions.forEach(sub => sub(e.clientX, e.clientY));
  }
  subscribe(callback) {
    this.subscriptions.push(callback);    

    return () => {
      this.subscriptions = this.subscriptions.filter(cb => cb !== callback);
    }
  }
}

Step 2. Create HTML elements

We now create our HTML elements for circle and textMessageBox and add styles to them.

<div class="container">
  <div class="circle" ></div>
  <div class="mouse-position">
  <h4>Mouse Position</h4>
  <div class="position"></div>
</div>
</div>
.container {
  position: relative;
  width: 100vw;
  height: 100vh;
  background-color: #f3df49;
}
.circle {
  position: absolute;
  background-color: #238643;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  z-index: 2;
}

.mouse-position {
  position: fixed;
  top: 20px;
  right: 20px;
  width: 200px;
  height: 100px;
  background-color: black;
  border-radius: 4px;
  padding: 4px 16px;
  color: white;
}

.mouse-position h4 {
  color: white;
  margin: 10px 0;
}

Step 3. Add observers

The last step to make it come together is to create an instance of our MousePositionObservable class and add observers to it.

To do that we shall invoke the subscribe method on the class instance and pass on a callback.

Our code looks like the below:

const mousePositionObservable = new MousePositionObservable();

mousePositionObservable.subscribe((x, y) => {
  const circle = document.querySelector('.circle');
   window.setTimeout(() => {
     circle.style.transform = `translate(${x}px, ${y}px)`;
   }, 1000);
});

// Update the mouse positon container to show the mouse position values
mousePositionObservable.subscribe((x, y) => {
  const board = document.querySelector('.mouse-position .position');
  board.innerHTML = `
    <div>
       <div>ClientX: ${x}</div>
       <div>ClientY: ${y}</div>
    </div>
  `
})

We add two subscriptions to the MousePositionObservable instance, one for each element that needs to listen to mouse values.

The subscription callback for the circle element gets the reference of the DOM element and updates its transform property. The transform property will use hardware acceleration where possible, so using translate() over position top and left will see performance benefits if any animations or transitions are also being used on the element.

The subscription callback for the textbox element updates its HTML content by using the innerHTML property.

Note: When you want to unsubscribe the listener, all you have to do is to store the returned value from the subscribe function call and invoke it like a function

That is all we need for our demo.

You can check out the working example in the Codepen below:


Advantages and Disadvantages of the Observer Design Pattern

An Observer design pattern provides us the following benefits:

  • It is extremely useful when we want to perform multiple actions on a single event.

  • It provides a way to decouple functionalities while maintaining consistency between related objects.

The downside of this pattern stems from its benefits:

  • Since the Observer design pattern leads to loosely coupled code, it is sometimes hard to guarantee that other parts of the application are working as they should. For example, the subscriptions added to the subject may have code that is behaving incorrectly, but there is no way for the publisher to know that.

Real-World Applications

While working with web development we see that Redux and React Context are both examples of implementations built upon the Observer Design Pattern.

In Redux, we have a subscribe method that allows us to add observers to the redux state which acts as the subject. Whoever subscribes to the redux store is notified when any change is made to the store.

Similarly, with React Context whenever the value is updated for the ContextProvider, all components that subscribe to the Context either through the useContext hook or through Context.Consumer are re-rendered with updated context values.


Conclusion

In this article, we went through the Observer design pattern and how to use it within our application. We also implement a demo based on this pattern and learned about some of the advantages and disadvantages of following this approach to designing interactions.

Thank you for reading.

If you found this article useful and informative, please don't forget to like and share it with your friends and colleagues.

If you have any suggestions, please feel free to comment.

Follow me on Twitter for more web development content.

Did you find this article valuable?

Support Frontend Delight by becoming a sponsor. Any amount is appreciated!