DEV Community

Cover image for Observer Design Pattern in JavaScript
Shubham Khatri
Shubham Khatri

Posted on • Edited on • Originally published at betterprogramming.pub

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);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode
.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;
}
Enter fullscreen mode Exit fullscreen mode

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>
  `
})
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (11)

Collapse
 
shiznit013 profile image
shiznit013

Maybe I'm reading this wrong, but I think there's a bug in your unsubscribe function. If the predicate in the filter is true, then the item won't be filtered. I think you want !==.

Collapse
 
ashiquedesai profile image
Ashique

Thank you for pointing this out...it drove me crazy for an hour at least :)

Collapse
 
shubhamreacts profile image
Shubham Khatri

You are correct, thanks for pointing it out. I have updated it in the post.

Collapse
 
ashiquedesai profile image
Ashique • Edited

This is a really great article on the Observer pattern. To the point and very visual as well.
The bug mentioned by dev.to/shiznit013 "( .filter( !== instead of === )" was fixed in the first example but it still remains in the final example in Code-pen. This really drove me crazy for a few minutes till I read Shiznit013's comment.
But, other than that little bug, this is a beautiful piece. Thanks so much, for taking the time and effort to write this.

Collapse
 
maniator profile image
Naftali Lubin

You might want to check out my servable observable :-)

servable.serveside.dev

Created it a couple years ago while reading the rx docs

It does need some polish

Collapse
 
shubhamreacts profile image
Shubham Khatri

It does look really good 🙂

Collapse
 
maniator profile image
Naftali Lubin

Thanks :-)

Collapse
 
dimitrisauvage profile image
Dimitri Sauvage

No word about RXJS which impléments this pattern for a long time…
Angular uses rxjs in his core :)

Collapse
 
shubhamreacts profile image
Shubham Khatri

I would have loved to mention it but I haven't used RxJS or Angular.
I will definitely research and update the post in near future

Collapse
 
fridaycandours profile image
Friday candour

Great content, I find it very 7

Collapse
 
shubhamreacts profile image
Shubham Khatri

Thanks a lot. It feels good to hear this 😃