18

Hi I'm tyring to implement observer pattern in JavaScript:

My index.js:

$(document).ready(function () {
  var ironMan = new Movie();
  ironMan.setTitle('IronMan');
  ironMan.setRating('R');
  ironMan.setId(1);
  //  ironMan.setCast(['Robert Downey Jr.', 'Jeff Bridges', 'Gwyneth Paltrow']);

  var terminator = new Movie();
  terminator.setTitle('Terminator');
  terminator.setRating('P');
  terminator.setId(2);

  console.log(ironMan.toString());
  console.log(terminator.toString());

  ironMan.play();
  ironMan.stop();
  ironMan.download();
  ironMan.share('V. Rivas');

  console.log(ironMan.getCast()[0]);
});

My movie:

var title;
var rating;
var id;
var observers;


function Movie() {
  observers = new ObserverList();
}

//function Movie (title, rating, id){
//  this. title = title;
//  this.rating =  rating;
//  this.id =id;
//  observers = new ObserverList();
//}

Movie.prototype.setTitle = function (newTitle) {
  this.title = newTitle;
}

Movie.prototype.getTilte = function () {
  return this.title;
}

Movie.prototype.setRating = function (newRating) {
  this.rating = newRating;
}

Movie.prototype.getRating = function () {
  return this.rating;
}

Movie.prototype.setId = function (newId) {
  this.id = newId;
}

Movie.prototype.getId = function () {
  return this.id;
}

Movie.prototype.play = function () {
  for (i = 0; i < observers.Count; i++) {
    console.log("palying...");
  }
}

Movie.prototype.stop = function () {
  for (i = 0; i < observers.Count; i++) {
    console.log("stoped");
  }
}

Movie.prototype.AddObserver = function (observer) {
  observers.Add(observer);
};

Finally observer:

function ObserverList() {
  this.observerList = [];
}

ObserverList.prototype.Add = function (obj) {
  return this.observerList.push(obj);
};

ObserverList.prototype.Empty = function () {
  this.observerList = [];
};

ObserverList.prototype.Count = function () {
  return this.observerList.length;
};

ObserverList.prototype.Get = function (index) {
  if (index > -1 && index < this.observerList.length) {
    return this.observerList[index];
  }
};

ObserverList.prototype.Insert = function (obj, index) {
  var pointer = -1;

  if (index === 0) {
    this.observerList.unshift(obj);
    pointer = index;
  } else if (index === this.observerList.length) {
    this.observerList.push(obj);
    pointer = index;
  }

  return pointer;
};

Any help you can provide will me most grateful.

Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46
Ignacio Garat
  • 1,536
  • 6
  • 24
  • 48

10 Answers10

30

JavaScript is event-driven: That means it's aware of time and expects things to change over time. The original Observer Pattern was created for languages like C++ that aren't aware of time. You can leverage JavaScript's strengths by using a game loop to check for state changes.

Create two DOM elements, an input and output

<input type="text" value="Enter some text...">
<p id="output">

Set up a requestAnimationFrame loop and start observing.

//Get a reference to the input and output
var input = document.querySelector("input");
var output = document.querySelector("#output");

//Set up a requestAnimationFrame loop
function update () {
  requestAnimationFrame(update);
  
  //Change the output to match the input
  output.innerHTML = input.value;
}
update(); 

This is what game engines do for immediate mode rendering. It's also what the React framework does to check for state changes in the DOM.

(If you need it, here's a simple requestAnimationPolyfill)

//Polyfill for requestAnimationFrame
window.requestAnimationFrame = (function(){
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          window.oRequestAnimationFrame      ||
          window.msRequestAnimationFrame     ||
          function(/* function */ callback, /* DOMElement */ element){
            window.setTimeout(callback, 1000 / 60);
          };
})();
danronmoon
  • 3,814
  • 5
  • 34
  • 56
d13
  • 9,817
  • 12
  • 36
  • 44
14

In JavaScript, there is no point to implement pure observer pattern as in Java, because JavaScript has this little thing called functional programming. So just use something like http://api.jquery.com/category/callbacks-object/ instead of your ObserverList.

If you still want to use your object, then everything depends on what do you want to pass to ObserverList.Add. If it is some object, then you need to write

for( i = 0; i < observers.Count; i++) { 
  observers[i].Notify("some data"); 
}

If it is a function then you need to write

for( i = 0; i < observers.Count; i++) { 
  observers[i]("Some data"); 
}

Also you can use Function.apply() or Function.call() to supply this to your function

Dmitry Osinovskiy
  • 9,999
  • 1
  • 47
  • 38
  • I'll check it out, but if I wanted to make it work as it is, what shoudl I do? – Ignacio Garat Sep 06 '12 at 21:18
  • HI Thanks Dmitry, where should I do the add? and which one should I use? the one in the in the observer class or the movie class? thank you very much – Ignacio Garat Sep 06 '12 at 21:51
  • HI Thanks Dmitry, I'm calling the ObserverList.Add form Movie.AddObserver,where should I call this Movie.AddObserver and and which parameters should I pass? Thank you very much dimitri? – Ignacio Garat Sep 06 '12 at 21:57
  • This all depends on why do you need this pattern in the first place. Who should observe events for your movies? – Dmitry Osinovskiy Sep 07 '12 at 06:31
  • There are some cases where a broadcast pattern would be useful. For example, let's say you have an interface with a bunch of objects that modify themselves based on changes to one element (for example, some kind of complex calculator). It would be useful to just broadcast an event like `factorChanged` that each element of that form could listen for and then adjust themselves accordingly. – Jordan Reiter Dec 12 '12 at 20:42
  • @Jordan Reiter Wouldn't callbacks object from jQuery which I linked to in my answer suffice? – Dmitry Osinovskiy Dec 13 '12 at 14:19
  • It's a bit different. With callbacks looks like *everything* in the callback gets called. – Jordan Reiter Dec 13 '12 at 16:25
  • 7
    The observer pattern is not replaceable by callbacks. The idea with the observer pattern is that you can see when an object changes, its not simply triggering an event or calling a callback whenever you want to. – B T Feb 05 '15 at 03:00
  • @BT but take a look at this page: https://api.jquery.com/jQuery.Callbacks/ ... as far as I understand it, if you scroll down to the bit about "Publish/Subscribe, or, the Observer pattern" you can implement a very nice observer pattern, using JQ callbacks. Is there anything missing from that implementation? – mike rodent Jul 30 '17 at 18:20
  • 1
    @mikerodent There's lots of ways to implement the observer pattern, but the pattern itself is not the same as pub/sub. The observer pattern is a subset of the pub/sub pattern - its more specific. – B T Aug 04 '17 at 19:49
4

Here's an implementation of the Observer pattern in JavaScript that provides an API very similar to Backbone Models. This implementation avoids use of "this" and "new", as suggested by Douglas Crockford.

// The constructor function.
function Model(){

  // An object containing callback functions.
  //  * Keys are property names
  //  * Values are arrays of callback functions
  var callbacks = {},

      // An object containing property values.
      //  * Keys are property names
      //  * Values are values set on the model
      values = {};

  // Return the public Model API,
  // using the revealing module pattern.
  return {

    // Gets a value from the model.
    get: function(key){
      return values[key];
    },

    // Sets a value on the model and
    // invokes callbacks added for the property,
    // passing the new value into the callback.
    set: function(key, value){
      values[key] = value;
      if(callbacks[key]){
        callbacks[key].forEach(function (callback) {
          callback(value);
        });
      }
    },

    // Adds a callback that will listen for changes
    // to the specified property.
    on: function(key, callbackToAdd){
      if(!callbacks[key]){
        callbacks[key] = [];
      }
      callbacks[key].push(callbackToAdd);
    },

    // Removes a callback that listening for changes
    // to the specified property.
    off: function(key, callbackToRemove){
      if(callbacks[key]){
        callbacks[key] = callbacks[key].filter(function (callback) {
          return callback !== callbackToRemove;
        });
      }
    }
  };
}

Here's some example code that uses Model:

// Create a new model.
var model = Model();

// Create callbacks for X and Y properties.
function listenX(x){
  // The new value is passed to the callback.
  console.log('x changed to ' + x);
}

function listenY(y){
  // The new value can be extracted from the model.
  console.log('y changed to ' + model.get('y'));
}

// Add callbacks as observers to the model.
model.on('x', listenX);
model.on('y', listenY);

// Set values of X and Y.
model.set('x', 30); // prints "x changed to 30"
model.set('y', 40); // prints "y changed to 40"

// Remove one listener.
model.off('x', listenX);
model.set('x', 360); // prints nothing
model.set('y', 50); // prints "y changed to 40"
Community
  • 1
  • 1
curran
  • 1,261
  • 13
  • 8
  • This is the object literal pattern. For the revealing module pattern, both the functions get, set would be defined in the closure, and you return an object at the end of the form `return {get: get, set: set};` – I-Lin Kuo Apr 03 '14 at 07:51
  • Your solution is more a Publish–subscribe pattern https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern – Alex P. Aug 14 '15 at 12:15
3
class EventObserver {
  constructor () {
    this.observers = []
  }

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

  unsubscribe (fn) {
    this.observers = this.observers.filter(subscriber => subscriber !== fn)
  }

  broadcast (data) {
    this.observers.forEach(subscriber => subscriber(data))
  }
}

Or you can use EventEmitter in NodeJs https://nodejs.org/api/events.html#events_class_eventemitter

zloctb
  • 10,592
  • 8
  • 70
  • 89
  • Not knowing the type in Javascript is very confusing but this implementation is very clean and helpful. Thanks! – Lucas Sousa Dec 27 '19 at 18:58
2

Below is an implementation i adapted a little from the book Learning Javascript Design Patterns.

function pubsub(obj) {

var events = {},
    subUid = -1;

obj.publish = function (event, args) {

    if (!events[event]) {
        return false;
    }

    var subscribers = events[event],
    len = subscribers ? subscribers.length : 0;

    while (len--) {
        subscribers[len].func(event, args);
    }
};

obj.subscribe = function (event, func) {

    if (!events[event]) {
        events[event] = [];
    }

    var token = (++subUid).toString();
    events[event].push({
        token: token,
        func: func
    });

    return token;

};

obj.unsubscribe = function (token) {

    for (var event in events) {
        if (events.hasOwnProperty(event)) {
            for (var i = 0, j = events[event].length ; i < j ; i++) {
                if (events[event][i].token === token) {
                    events[event].splice(i, 1);
                }
            }
        }
    }

    return this;

};

}

var obj = {}; // Any javascript object
pubsub(obj); // Make an observable from the given object

var subscription = obj.subscribe('load', handler);

// event handler callback
function handler(event, data) {
    console.log(event, data);
}

obj.publish('load', 'Data loaded successfully'); // logs 'load Data loaded successfully'
obj.unsubscribe(subscription);
obj.publish('load', 'Data loaded successfully'); // nothing happens

Cheers!

zak.http
  • 316
  • 4
  • 4
2

class Observable {
    constructor() {
       this.observer = []; 
    }
    subscribe(item) {
        this.observer.push(item);
    }
    unsubscribe(item) {
        if(!this.observer) return 'empty';
        else {
            this.observer.filter(subscribe => subscribe !== item);
        }
    }
    notify(data) {
        this.observer.forEach(item => item(data));
    }
}

var p1 = document.querySelector('.p1');
var p2 = document.querySelector('.p2');
var p3 = document.querySelector('.p3');
var input = document.querySelector('.input');

const update1 = text => p1.textContent = text;
const update2 = text => p2.textContent = text;
const update3 = text => p3.textContent = text;

var observarble = new Observable();
observarble.subscribe(update1);
observarble.subscribe(update2);
observarble.subscribe(update3);

input.addEventListener('keyup', event => observarble.notify(event.target.value));
<input type="input" class="input" />
<div class="p1"></div>
<div class="p2"></div>
<div class="p3"></div>

Observer is one of the popular patterns that use across all javascript applications.

The instance (subject) maintains a collection of objects (observers) and notifies them all when changes to the state occur.

Let's explain by writing some logic

class Observable {
    constructor() {
       this.observer = []; 
    }
    subscribe(item) {
        this.observer.push(item);
    }
    unsubscribe(item) {
        if(!this.observer) return 'empty';
        else {
            this.observer.filter(subscribe => subscribe !== item);
        }
    }
    notify(data) {
        this.observer.forEach(item => item(data));
    }
}

Now your question will be what's next ??

Where to actually use this pattern.

Imagine that you have to update multiple elements simultaneously when some event occurs.

Add some HTML in your code

<input type="input" class="input" />
<div class="p1"></div>
<div class="p2"></div>
<div class="p3"></div>

Get those nodes using Javascript

var p1 = document.querySelector('.p1');
var p2 = document.querySelector('.p2');
var p3 = document.querySelector('.p3');
var input = document.querySelector('.input');

To set the value using observer you need to add their text content

const update1 = text => p1.textContent = text;
const update2 = text => p2.textContent = text;
const update3 = text => p3.textContent = text;


var observarble = new Observable();
observarble.subscribe(update1);
observarble.subscribe(update2);
observarble.subscribe(update3);

One last thing attach an event listener with input keyup/change

input.addEventListener('keyup', ev => observarble.notify(ev.target.value));

That's it :) !!

Link for working demo https://codepen.io/nishant5857/pen/MWKdByY

  • const update1 = text => p1.textContent = text; - This is the key point, it includes `this` context when execute the callback function and also keep track for `unsubscribe` task. – Nguyen Tran Feb 26 '21 at 04:20
0

For me this is the best way to implement an Observer pattern in JS

function Click() {
    this.handlers = [];  // observers
}

Click.prototype = {

    subscribe: function(fn) {
        this.handlers.push(fn);
    },

    unsubscribe: function(fn) {
        this.handlers = this.handlers.filter(
            function(item) {
                if (item !== fn) {
                    return item;
                }
            }
        );
    },

    fire: function(o, thisObj) {
        var scope = thisObj || window;
        this.handlers.forEach(function(item) {
            item.call(scope, o);
        });
    }
}

// log helper

var log = (function() {
    var log = "";

    return {
        add: function(msg) { log += msg + "\n"; },
        show: function() { alert(log); log = ""; }
    }
})();

function run() {

    var clickHandler = function(item) { 
        log.add("fired: " + item); 
    };

    var click = new Click();

    click.subscribe(clickHandler);
    click.fire('event #1');
    click.unsubscribe(clickHandler);
    click.fire('event #2');
    click.subscribe(clickHandler);
    click.fire('event #3');

    log.show();
}
GutiMac
  • 2,402
  • 1
  • 20
  • 27
  • what happens if more then one listener with the same functionality is being attached? lets say methodA and methodB both subscribe the same function. – Rafael Herscovici Jan 29 '17 at 18:34
0

The observer pattern is all about updating an object and having those updates automatically send out an event that gives info about what was updated.

For example:

function ObserverList(){
  this.observerList = []
  this.listeners = []
}

ObserverList.prototype.add = function( obj ){
  this.observerList.push(obj)
  this.listeners.forEach(function(callback){
    callback({type:'add', obj:obj})
  })
}

ObserverList.prototype.onChange = function(callback){
  this.listeners.push(callback)
}

Here's a module of the observer pattern in javascript, you can take a look at the source code fore more info: https://github.com/Tixit/observe

B T
  • 57,525
  • 34
  • 189
  • 207
0

This is old, but I wanted to provide an answer to the original question, "how to implement the observer pattern given the existing code".

The Observer pattern can be simplified as a communication design where the target (the thing being observed) has a pointer to the observer(s) and assumes a public API for an observer. For example, the target assumes that an observer has a method called update or that an observer is a Function. It's how a target notifies observers of changes, by actually calling a method on the observer object (or Function if the observer is a function).

Any time a property is mutated or changed, the target must update all observers that have registered to be notified.

Here, I'm assuming that you want to know how to implement a Key-Value Observer. In which case, the code will have to iterate over its observer list and call each observers update method (or just execute the observer in the case where it's a Function) when a property is changed.

var observers = null;

function Movie() {
 observers = new ObserverList();
}

Movie.prototype.changed = function(key, old, value){
  // Assumption here is that observers can observe individual properties.
  if(!this.observers[key]) return
  
  this.observers[key].forEach( o => {
    // Assumption is that observers have an update method. This is the only
    // thing the target knows about an observer.
      o.update(key, old, value, this)
  })

}

// Now every setter on the target has to notify the observers by calling `changed`
Movie.prototype.setTitle = function (newTitle) {
  var old = this.title;
  this.title = newTitle;
  this.changed("title", old, this.title, this)
}

you'd have to add that changed method and then update all the setter methods to call changed as per above.

I also noticed that there's nowhere in the original code that observes the movie. You'll need to add code that actually observes it, something that implements update, given my example above.

I'm unsure what the intent is for play and stop.

Joey Guerra
  • 596
  • 6
  • 11
0

Rather than implementing your own observer pattern, you can use RxJS which provides you an Observable API.

A very simplistic version of this without relying on a full library but should allow you to migrate to the whole RxJS stack in the future can be written as follow (it does not try to do the complete and error callbacks)

/**
 * Basic Observable, made compatible with RX-JS API.
 */
type Observer<T> = {
  next: (value: T) => void;
};

interface Subscription {
  unsubscribe(): void;
}

export class Observable<T> {
  private observers: Observer<T>[] = [];
  subscribe(next: (value: T) => void): Subscription {
    const observer = { next }
    this.observers.push(observer);
    return {
      unsubscribe: () => {
        this.observers = this.observers.filter((h) => h !== observer);
      },
    };
  }
  next(data: T) {
    this.observers.forEach((h) => h.next(data));
  }
}

And tested as follows:

import { Observable } from './Observable';
import noop from 'noop-fn';
describe('Observable', () => {
  it('can be created', () => {
    new Observable();
  });
  it('can be subscribed to', () => {
    const o = new Observable();
    const subscription = o.subscribe(noop);
    subscription.unsubscribe();
  });
  it('can receive notifications', () => {
    const o = new Observable();
    const handler = jest.fn();
    const subscription = o.subscribe(handler);
    o.next('Hello');
    subscription.unsubscribe();
    expect(handler.mock.calls.length).toBe(1);
    expect(handler.mock.calls[0][0]).toBe('Hello');
  });
});
Archimedes Trajano
  • 35,625
  • 19
  • 175
  • 265