DEV Community

Nishan Bajracharya
Nishan Bajracharya

Posted on • Originally published at Medium on

4 1

Implementing the Observer pattern in JavaScript

Shamelessly borrowed from Wikipedia

The observer pattern is a design pattern that observes an entity and on change, notifies all subscribers of the change. It is very simple to understand and is very effective for when a change in an entity needs to trigger many events.

How does the observer pattern work?

The observer pattern is a combination of two elements, a Subject, and an Observer. The Subject tracks the value of an entity and the Observer listens for changes tracked by the Subject.

When an entity tracked by the Subject changes, the Subject notifies the Observer of the change. One Subject can have many Observers, in which case it notifies all the Observers of the change in the entity. The observers can be subscribed to trigger events when the Subject notifies them.

So how do you implement the observer pattern in JavaScript?

Let’s start with the Subject, which is alternatively called an Observable. The subject contains a state and a collection of observers that it notifies to when the state changes.

The subject provides methods to register and unregister observers and a method to notify all the observers.

class Subject {
constructor(state) {
this.state = state;
this.observers = [];
}
getState() {
return this.state;
}
setState(state) {
this.state = state;
this.notifyObservers(this.state);
}
registerObserver(observer) {
this.observers.push(observer);
}
unregisterObserver(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notifyObservers(data) {
this.observers.forEach(observer => observer.notify(data));
}
}
view raw Subject.js hosted with ❤ by GitHub

Each observer has a notify method which the subject calls when it needs to notify observers for change in state.

Next, let’s look at the Observer. It simply only contains a notify method but we add a subscribe method that is used to subscribe an event to the Observer. When the Subject notifies the Observer of change in state, the Observer triggers the subscribers.

class Observer {
constructor(subject) {
subject.registerObserver(this);
this.subscribers = [];
}
subscribe(subscriber) {
this.subscribers.push(subscriber);
}
notify(data) {
this.subscribers.forEach(subscriber => subscriber(data));
}
}
view raw Observer.js hosted with ❤ by GitHub

The subscriber is a function that gets called when the Observer’s notify function is called.

How do you use it?

We create a new subject that tracks the value of an entity and an observer for the subject an subscribe to the observer. Then when there is a change in the subject’s state, all the observers are notified and their subscribers are triggered.

const observable = new Subject(10);
const observerA = new Observer(observable);
const observerB = new Observer(observable);
observerA.subscribe(data => {
console.log("observerA", data);
});
observerB.subscribe(data => {
console.log("observerB", data);
});
observable.setState(20);
observable.setState(30);
observable.setState(20);
/* Output
observerA 20
observerB 20
observerA 30
observerB 30
observerA 20
observerB 20
*/

The subject has two observers and each observer is subscribed by two functions. When the observable changes the state by calling the setState method, both the observers are notified and each of their subscribers are triggered.

And that is how the observer pattern works and how it is implemented in JavaScript.

An alternate and simpler approach

While the above approach to implementing the observer pattern is more than enough, it requires creating at least one instance of the subject and one instance of the observer to work. So we can simplify the pattern using a single Observer class that maintains the state and also triggers the subscribers of the observer.

const observer = new Observer();
// Returns a function which, when called, unsubscribes from the observer.
const unsubscribe = observer.subscribe(data => {
console.log("SingleSubscribe", data);
});
const subscriberA = data => console.log("A", data);
const subscriberB = data => console.log("B", data);
// Returns an array of unsubscribe functions
observer.subscribe([subscriberA, subscriberB]);
observer.set(10);
// To unsubscribe the observer
// unsubscribe()
view raw index.js hosted with ❤ by GitHub
class Observer {
constructor(state) {
this.state = state;
this.subscribers = [];
}
get() {
return this.state;
}
set(state) {
this.state = state;
this.broadcast(state);
}
subscribeMany(fnArray = []) {
return fnArray.map(fn => this.subscribe(fn));
}
subscribe(fn) {
if (Array.isArray(fn)) {
return this.subscribeMany(fn);
}
this.subscribers.push(fn);
return () =>
(this.subscribers = this.subscribers.filter(
subscriber => subscriber !== fn
));
}
broadcast(data) {
this.subscribers.forEach(subscriber => subscriber(data));
return data;
}
}
view raw Observer.js hosted with ❤ by GitHub

What are some practical use cases of the Observer Pattern?

When done correctly, the observer pattern can be very powerful and extremely helpful for broadcasting information across an application. Some use cases of the observer pattern can be like so:

  • Group Messaging: When a message is sent to a group chat, all the participants of the chat receive the notification of the new message.
  • Website Newsletters: All subscribers of a website newsletter receive email notifications when the website publishes a new newsletter.
  • Redux: When a redux action is dispatched, all the subscribers of the store are notified.

References and Further Learning

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free