Conceptual Article

Observer Design Pattern in JavaScript

Published on September 21, 2020
author

Devan Patel

Observer Design Pattern in JavaScript

There are many times when one part of the application changes, other parts needs to be updated. In AngularJS, if the $scope object updates, an event can be triggered to notify another component. The observer pattern incorporates just that - if an object is modified it broadcasts to dependent objects that a change has occurred.

Another prime example is the model-view-controller (MVC) architecture; The view updates when the model changes. One benefit is decoupling the view from the model to reduce dependencies.

Observer Design Pattern Observer Design Pattern on Wikipedia

As shown in the UML diagram, the necessary objects are the subject, observer, and concrete objects. The subject contains references to the concrete observers to notify for any changes. The Observer object is an abstract class that allows for the concrete observers to implements the notify method.

Let’s take a look at an AngularJS example that encompasses the observer pattern through event management.

// Controller 1
$scope.$on('nameChanged', function(event, args) {
    $scope.name = args.name;
});

...

// Controller 2
$scope.userNameChanged = function(name) {
    $scope.$emit('nameChanged', {name: name});
};

With the observer pattern, it is important to distinguish the independent object or the subject.

It is important to note that although the observer pattern does offer many advantages, one of the disadvantages is a significant drop in performance as the number of observers increased. One of the most notorious observers is watchers. In AngularJS, we can watch variables, functions, and objects. The $$digest cycle runs and notifies each of the watchers with the new values whenever a scope object is modified.

We can create our own Subjects and Observers in JavaScript. Let’s see how this is implemented:

var Subject = function() {
    this.observers = [];

    return {
    subscribeObserver: function(observer) {
        this.observers.push(observer);
    },
    unsubscribeObserver: function(observer) {
        var index = this.observers.indexOf(observer);
        if(index > -1) {
        this.observers.splice(index, 1);
        }
    },
    notifyObserver: function(observer) {
        var index = this.observers.indexOf(observer);
        if(index > -1) {
        this.observers[index].notify(index);
        }
    },
    notifyAllObservers: function() {
        for(var i = 0; i < this.observers.length; i++){
        this.observers[i].notify(i);
        };
    }
    };
};

var Observer = function() {
    return {
    notify: function(index) {
        console.log("Observer " + index + " is notified!");
    }
    }
}

var subject = new Subject();

var observer1 = new Observer();
var observer2 = new Observer();
var observer3 = new Observer();
var observer4 = new Observer();

subject.subscribeObserver(observer1);
subject.subscribeObserver(observer2);
subject.subscribeObserver(observer3);
subject.subscribeObserver(observer4);

subject.notifyObserver(observer2); // Observer 2 is notified!

subject.notifyAllObservers();
// Observer 1 is notified!
// Observer 2 is notified!
// Observer 3 is notified!
// Observer 4 is notified!

Publish/Subscribe

The Publish/Subscribe pattern, however, uses a topic/event channel that sits between the objects wishing to receive notifications (subscribers) and the object firing the event (the publisher). This event system allows code to define application-specific events that can pass custom arguments containing values needed by the subscriber. The idea here is to avoid dependencies between the subscriber and publisher.

This differs from the Observer pattern since any subscriber implementing an appropriate event handler to register for and receive topic notifications broadcast by the publisher.

Many developers choose to aggregate the publish/subscribe design pattern with the observer though there is a distinction. Subscribers in the publish/subscribe pattern are notified through some messaging medium, but observers are notified by implementing a handler similar to the subject.

In AngularJS, a subscriber ‘subscribes’ to an event using $on(‘event’, callback), and a publisher ‘publishes’ an event using $emit(‘event’, args) or $broadcast(‘event’, args).

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products


Tutorial Series: JavaScript Design Patterns

Every developer strives to write maintainable, readable, and reusable code. Code structuring becomes more important as applications become larger. Design patterns prove crucial to solving this challenge - providing an organization structure for common issues in a particular circumstance.

The design pattern below is only one of many useful patterns that can help you level up as a JavaScript developer. For the full set, see JavaScript Design Patterns.

About the authors
Default avatar
Devan Patel

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
4 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

We could remove the return statements from our Subject and Observer functions. The main difference is that writing using an explicit return { ... } would expose our methods whereas with class of function constructors it wouldn’t (see my previous comment).

JavaScript is very dynamic and has always more than one way to write the same thing with very subtle diffs. 🤷‍♂️

const Subject = function () {
  let observers = []

  this.subscribe = function (observer) {
    observers.push(observer)
  }

  this.unsubscribe = function (observer) {
    let index = observers.indexOf(observer)
    if (index > -1) {
      observers.splice(index, 1)
    }
  }

  this.notify = function (observer) {
    let index = observers.indexOf(observer)
    if (index > -1) {
      observers[index].notify(index)
    }
  }

  this.broadcast = function () {
    for (let i = 0; i < observers.length; i++) {
      observers[i].notify(i)
    }
  }
}

const Observer = function () {
  this.notify = function (index) {
    console.log('Observer ' + index + ' is notified!')
  }
}

let subject = new Subject()
let observer = new Observer()

console.log(subject)
console.log(observer)

subject.subscribe(observer)
subject.notify(observer)
subject.broadcast()

Which would be very similar to ES6’s class syntax:

class Subject {
  constructor() {
    this.observers = []
  }

  subscribe(observer) {
    this.observers.push(observer)
  }

  unsubscribe(observer) {
    let index = this.observers.indexOf(observer)
    if (index > -1) {
      this.observers.splice(index, 1)
    }
  }

  notify(observer) {
    let index = this.observers.indexOf(observer)
    if (index > -1) {
      this.observers[index].notify(index)
    }
  }

  broadcast() {
    for (let i = 0; i < this.observers.length; i++) {
      this.observers[i].notify(i)
    }
  }
}

class Observer {
  notify(index) {
    console.log('Observer ' + index + ' is notified!')
  }
}

let subject = new Subject()
let observer = new Observer()

console.log(subject)
console.log(observer)

subject.subscribe(observer)
subject.notify(observer)
subject.broadcast()

Hey, @ayeshamalik8751 and @akinyeleolat, you’re right! Just remove this and should work fine. I’ve made a few changes so it’s less verbose.

const Subject = function () {
  let observers = []

  return {
    subscribe: function (observer) {
      observers.push(observer)
    },
    unsubscribe: function (observer) {
      let index = observers.indexOf(observer)
      if (index > -1) {
        observers.splice(index, 1)
      }
    },
    notify: function (observer) {
      let index = observers.indexOf(observer)
      if (index > -1) {
        observers[index].notify(index)
      }
    },
    broadcast: function () {
      for (let i = 0; i < observers.length; i++) {
        observers[i].notify(i)
      }
    },
  }
}

const Observer = function () {
  return {
    notify: function (index) {
      console.log('Observer ' + index + ' is notified!')
    },
  }
}

let subject = new Subject()
let observer = new Observer()

console.log(subject)
console.log(observer)

subject.subscribe(observer)
subject.notify(observer)
subject.broadcast()

this.observers has issue.

var Subject = function() {

    this.observers = []

    return {
    observers: this.observers,
    subscribeObserver: function(observer) {
        this.observers.push(observer);
    },
    unsubscribeObserver: function(observer) {
        var index = this.observers.indexOf(observer);
        if(index > -1) {
        this.observers.splice(index, 1);
        }
    },
    notifyObserver: function(observer) {
        var index = this.observers.indexOf(observer);
        if(index > -1) {
        this.observers[index].notify(index);
        }
    },
    notifyAllObservers: function() {
        for(var i = 0; i < this.observers.length; i++){
        this.observers[i].notify(i);
        };
    }
    };
};```

The above code has an error, we cannot access this.observers inside subscribeObserver and other similar methods.

this will be undefined and will give an error { Cannot read property ‘push’ of undefined }

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.