Observer Design Pattern in JavaScript

The Observer Pattern is about a “one-to-many” relationship from one object called Subject (or publisher) to one or more objects called Listeners (or observers) that get notified when the Subject changes state. Therefore, several listeners could depend on one object to change for them to perform any action.

Design patterns provide a common language for developers to communicate and solve problems. When the book Design Patterns: Elements of Reusable Object-Oriented Software came out, it set up the foundation for all developers to learn from the wisdom of more advanced and seasoned developers to solve problems the same way they did and that it is applicable to design instead of just a programming language.

This post covers the Observer Design pattern using JavaScript. However, the same ideas apply to any programming language and you can use this pattern when needed in your project.

The Observer Pattern is about a “one-to-many” relationship from one object called Subject (or publisher) to one or more objects called Listeners (or observers) that get notified when the Subject changes state. Therefore, several listeners could depend on one object to change for them to perform any action.

First, let’s create the Subject class which will be the one that will change state and notify its observers:

/***
 * Represents the individual subject that changes over time
 * and notifies the listeners about its state
 */
class Subject {
    /***
     * @constructor
     * @param {number} val - The internal value of the subject
     */
    constructor(val = 0) {
        this.value = val;
        this.listeners = [];
    }

    /***
     * Adds new  listener to the listeners list (observers)
     * @param {object} listener
     */
    addListener(listener) {
        this.listeners.push(listener);
    }

    /***
     * Removes a listeners from the list of listeners (observer)
     * @param {object} listener
     */
    removeListener(listener) {
        this.listeners = this.listeners.filter(item => item !== listener);
    }

    /***
     * Notifies all listeners that the value has changed
     */
    alertListeners() {
        this.listeners.forEach(listener => listener.callback(this.value));
    }

    /***
     * Changes the value of the Subject
     * @param value
     */
    setValue(value) {
        this.value = value;
        this.alertListeners(); //Here the listeners are notified
    }
}

his class has two attributes, one for the value that will be tracked and a list of listeners that are registered to be notified. As the code is very short, it is self-explanatory: listeners can add and remove themselves via the addListener and removeListener methods. Also, when the setValue changes value, it uses the “alertListeners” method to notify all the listeners.

We can create listener class(es) that could be alerted when there are changes in the Subject. The only thing required for this class to be notified is to create a callback method that will be used by the Subject when the value changes and pass it to the listener.

/***
 * Represents the Listener object that will be notified by the subject
 */

class Listener {
    /***
     * @constructor
     * @param {number} val - The internal value (myvalue) of the listener
     */
    constructor(val = 0) {
        this.myvalue = val;
    }

    /***
     * Receives the notification from the subject and its value to work with it
     * @param value
     */
    callback(value) {
        console.log(`${this.myvalue} + ${value} = ${this.myvalue + value}`);
    }
}

You could create as many classes and objects as needed. The only requirement is to have the callback method available so it could be notified. In the following example, we have two listeners that get notified and print in the console the sum of the value sent by the publisher and their internal value.

//Creates a subject with the internal value of 50
let mySubject = new Subject(50);
//Creates a listener with the internal value of 10
let myListener = new Listener(10);

//Nothing is displayed in the console because myListener 
//is not listening to the subject yet.
mySubject.setValue(100);

//myListener is subscribed to mySubject
mySubject.addListener(myListener);

//Changing the value of mySubject displays the addition on the console
mySubject.setValue(300); //10 + 300 = 310
mySubject.setValue(99); //10 + 99 = 109

//Creates a second listener with a value of 490 and subscribed to mySubject
let mySecondListener = new Listener(490);
mySubject.addListener(mySecondListener);

//Will display 10 + 150 = 160 from myListener 
//and 490 + 150 = 640 from mySecondListener
mySubject.setValue(150);

//Removes the first listener
//Changing the value of mySubject will only display 
//the message from mySecondListener
mySubject.removeListener(myListener);

//Displays 490 + 45 = 535
mySubject.setValue(45);

Take Advantage of JavaScript and Go Further

Now, it is your turn to improve the code and make it more flexible. Let’s say that you want to have more freedom to name the methods of your observers. Thus, they could be named anything you want and let the subject know what method to call in your object. Thus, you could have a method in one object called displayInConsole while another observer could have a displayOnPage. You could modify addListener to receive a second parameter with the name of the method. The methodName parameter has “callback” as the default value so in case it is not provided, the subject still knows how to call the method.

addListener(listener, methodName = “callback”) {
     let object = {
       "listener" : listener,
       "method" : methodName
     };
      this.listeners.push(object);
  }

Don’t forget to modify the notifyListeners method which will be calling the name of each listener individually without depending on the “callback” name.

notifyListeners() {
    this.listeners.forEach(item => {
       item.listener[item.method](this.value);
    });
  }

For practice, how would you remove the listeners now that the method has been modified to allow a method name? Can you implement it?

Good luck!

Have your say

This site uses Akismet to reduce spam. Learn how your comment data is processed.