Sebastian Gomez
Understanding the Observer Pattern in JavaScript
In this post we'll learn how to implement the observer pattern in JavaScript and when we can use it.
The observer pattern is one of the most widely used software design patterns in JavaScript. Many important applications build on it to define better architectures for web apps, so learning and using it is highly recommended.
The observer pattern: key concepts
The observer pattern is defined as a behavioral pattern in object oriented programming, and it is responsible for communication between objects.
You may also find it referred to as the publisher subscriber pattern or pub/sub, a name that already gives us a basic idea of what it does.
In simple terms, this pattern lets one object notify other objects (the subscribers or observers) when the state of another object (the publisher) changes.
Implementation in JavaScript
To implement the observer pattern in JavaScript, we start by creating a class called Publicador that contains the subscribe(), unsubscribe() and notify() methods. Here is the example code:
// Define the "Publicador" class
class Publicador {
// Constructor of the "Publicador" class
constructor() {
// Create a "subscriptors" property to store the list of subscribers
this.subscriptors = [];
}
// "subscribe" method to add a new subscriber to the list
subscribe(subscriptor) {
// Append the "subscriptor" argument to the end of the list
this.subscriptors.push(subscriptor);
}
// "unsubscribe" method to remove a subscriber from the list
unsubscribe(subscriptor) {
// Filter the list, keeping only those that don't match the argument
this.subscriptors = this.subscriptors.filter(
(item) => item !== subscriptor
);
}
// "notify" method to notify an event to every subscriber
notify(event) {
// Iterate over each element in the subscribers list
this.subscriptors.forEach((item) => {
// Call each subscriber, passing "this" as context and "event" as argument
item.call(this, event);
});
}
}Now let's imagine we'll use this Publicador definition for a newspaper that regularly publishes new editions.
const periodico = new Publicador();Let's think about the customers (the subscribers), who need to know when a new edition of the newspaper arrives. Initially, the customers are plain functions:
// Define the "Observer" function that takes an "edicion" argument
function Observer(edicion) {
// Log a message saying a new edition has arrived
console.log("A new edition arrived with the name: " + edicion);
}
// Use "subscribe" on the "periodico" object to register the "Observer" function
periodico.subscribe(Observer);
// Subscribe it again: "Observer" will be notified twice per event
periodico.subscribe(Observer);
// Notify all subscribers (both instances of "Observer")
// passing the string "Nueva edicion" as the argument
periodico.notify("Nueva edicion");If you run this code in the browser console or in Node.js, you'll see:
A new edition arrived with the name: Nueva edicion
A new edition arrived with the name: Nueva edicionAs a better approach, we could define a class for the customers that lets us create instances and have more granular control. We'll also define a specific method (buzon) to listen for new editions of the newspaper:
// Define the "Subscriptor" class
class Subscriptor {
// Constructor that takes an "id" argument
constructor(id) {
// Assign the "id" to the object's property
this.id = id;
// Log that a subscriber with this "id" has been created
console.log("Subscriber #: " + id + " has been created");
}
// "buzon" method that takes an "edicion" argument
buzon(edicion) {
// Log that this subscriber received a new edition
console.log(
"Subscriber # " + this.id + " received a new edition: " + edicion
);
}
}
// Create three instances of "Subscriptor"
const subscriptor1 = new Subscriptor(1);
const subscriptor2 = new Subscriptor(2);
const subscriptor3 = new Subscriptor(3);Now comes the key part that completes the example: subscribing each instance's buzon method. Because notify invokes each subscriber with item.call(this, event), we need this inside buzon to keep pointing at the correct instance. To do that we use .bind, which pins the context:
// Subscribe each instance's "buzon" method, pinning its context with .bind
periodico.subscribe(subscriptor1.buzon.bind(subscriptor1));
periodico.subscribe(subscriptor2.buzon.bind(subscriptor2));
periodico.subscribe(subscriptor3.buzon.bind(subscriptor3));
// Notify all subscribers about the new edition
periodico.notify("June Edition");When you run it, you'll see something like this:
Subscriber #: 1 has been created
Subscriber #: 2 has been created
Subscriber #: 3 has been created
Subscriber # 1 received a new edition: June Edition
Subscriber # 2 received a new edition: June Edition
Subscriber # 3 received a new edition: June EditionNote: Save the bound function if you later want to unsubscribe a subscriber. Every call to .bind creates a brand new function, so unsubscribe must receive the exact same reference you used when subscribing:
>
``js const buzon1 = subscriptor1.buzon.bind(subscriptor1); periodico.subscribe(buzon1); //...later periodico.unsubscribe(buzon1); ``
This way we get more control over the subscribers and can subscribe or unsubscribe them more cleanly.
Conclusion
The observer pattern is easy to implement in JavaScript and its usefulness is almost immediate. With this pattern we can effectively manage communication between objects while keeping our code clean and organized.
Exercises to practice
- Implement the observer pattern in an instant messaging app, where users subscribe to a group chat and get notifications when there are new messages.
- Build an event notification system for a calendar app, where users can subscribe to events and receive reminders when they are about to happen.
- Design a temperature monitoring system for a set of sensors, where each sensor is an observer and a central controller is the publisher. When the temperature changes on a sensor, the controller is notified and can make decisions based on the changes.
3-point summary
- The observer pattern is a behavioral pattern in object oriented programming that makes communication between objects easy, notifying a set of objects (the subscribers) whenever the state of another object (the publisher) changes.
- In JavaScript we can implement the observer pattern with classes and methods like
subscribe(),unsubscribe()andnotify(). - Using the observer pattern in your projects gives you more granular control and keeps your code clean and organized.
That's all, I hope this post is useful to you and that you can apply it to a project you have in mind. If you have any questions, suggestions, or want to share your experiences implementing this pattern, don't hesitate to leave me a comment. And remember, if you liked it, you can also share it using the social links below. Good luck with your projects!
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.