Jump to content

An Observer Pattern Component in TypeScript/JavaScript


yahiko
 Share

Recommended Posts

Hi,

I have implemented a simple Observer Pattern library in TypeScript/JavaScript which does not require inheritance.

I have called it Paon (French name for a bird with many "eyes" on its feathers).

It is available in my GitHub and in npm.

Any feedback would be greatly appreciated.

Paon

An Observer Pattern Component in TypeScript/JavaScript.

  • No dependencies.
  • No inheritance is required.
  • Observable is just a component inside an object.
  • Observers are just functions.

Installation

npm install paon

To compile the TypeScript source to JavaScript, you would need to install the TypeScript compiler:

npm install -g typescript

To generate the minified JavaScript version when building, you would need to install uglifyjs:

npm install -g uglifyjs

Build

Resulting files are created in the dist/ folder.

Complete build (compilation and minification):

npm run build

Simple compilation (no minification):

npm run compile

Usage

All constants, interfaces, classes and functions are accessible inside the Paon namespace.

Simple example

Here is a simple example where we add an observable component inside a class Subject:

/// <reference path="paon.d.ts" />

class Subject {
    private name: string;
    observable: Paon.Observable; // Observer Pattern component

    constructor(name: string) {
        this.name = name;
        this.observable = new Paon.Observable(); // Instanciation/Initialization
    }

    changeName(name: string): string {
        this.name = name;
        this.observable.notifyObservers("nameChanged"); // A message is sent to observers
        return this.name;
    }
}

function onNameChanged() {
    alert("Name has changed");
}

let subject = new Subject("Penelope");

subject.observable.addObserver("nameChanged", onNameChanged); // Function onNameChanged() subscribes to subject's messages "nameChanged"

subject.changeName("Melissa");
// An alert popup appears: "Name has changed"

Above, in the class Subject, the method changeName() will send a "nameChanged" message to the instance's observers. After the instanciation of Subject, the function onNameChanged() subscribes to subject's messages "nameChanged". Therefore, when changeName() is called, an alert popup appears.

As we can see, with such a pattern, no inheritance with extends or implements is required. Just simple composition.

Example with extra data

We can send extra data to observers as we can see below:

/// <reference path="paon.d.ts" />

class Subject {
    private name: string;
    observable: Paon.Observable; // Observer Pattern component

    constructor(name: string) {
        this.name = name;
        this.observable = new Paon.Observable(); // Instanciation/Initialization
    }

    changeName(name: string): string {
        this.name = name;
        this.observable.notifyObservers("nameChanged", { data: name }); // A message with extra data is sent to observers
        return this.name;
    }
}

function onNameChanged(msg: { data: string }) {
    alert("Name has changed into " + msg.data);
}

let subject = new Subject("Penelope");

subject.observable.addObserver("nameChanged", onNameChanged); // Function onNameChanged() subscribes to subject's messages "nameChanged"

subject.changeName("Melissa");
// An alert popup appears: "Name has changed into Melissa"

The parameter msg in function onNameChanged() contains the extra data we have sent via method changeName(). Here, this is an object with a property data, but this could be anything.

Module Importation

This library can also be imported as a module with the import statement:

import Paon from "./paon"; // Declaration file .d.ts location

class Subject {
    private name: string;
    observable: Paon.Observable; // Observer Pattern component

    constructor(name: string) {
        this.name = name;
        this.observable = new Paon.Observable(); // Instanciation/Initialization
    }

    changeName(name: string): string {
        this.name = name;
        this.observable.notifyObservers("nameChanged", { data: name }); // A message with extra data is sent to observers
        return this.name;
    }
}

function onNameChanged(msg: { data: string }) {
    alert("Name has changed into " + msg.data);
}

let subject = new Subject("Penelope");

subject.observable.addObserver("nameChanged", onNameChanged); // Function onNameChanged() subscribes to subject's messages "nameChanged"

subject.changeName("Melissa");
// An alert popup appears: "Name has changed into Melissa"

Only the import statement differs from previous examples. Otherwise, the code is the same.

API Documentation

Add an observer to a type of message (similar to the DOM function addEventListener()):

Paon.Observable.addObserver(type: string, observer: Observer): Observer;

Remove an observer from a type of message (similar to the DOM function removeEventListener()):

Paon.Observable.removeObserver(type: string, observer: Observer): void;

Remove all observers from a type of message:

Paon.Observable.removeObserversType(type: string): void;

Send a message to observers (similar to the DOM function dispatchEvent()):

Paon.Observable.notifyObservers(type: string, msg?: any): void;

Contributors

yahiko

Licence

MIT

Link to comment
Share on other sites

Hi Yahiko,

Good job writing a library and getting out there but I'm not clear from your documentation how your implementation differs or betters the 4 million libraries already on npm that implement pub/sub? 

Is yours faster or safer? I didn't see any test cases or benchmarks in the repo.

I'd be careful using the term Observable as well, whilst observables and pub/sub are largely entwined JS has a spec proposal for Observables based on the community work from the large number of functional libraries implementing streams using observable terminology and your implementation does not match any of those specifications so could be a barrier to adoption.

It looks like you're wanting to create something to help with 2-way data binding for those who still think thats a good idea but from your examples you're expecting the consumer to do all the wiring themselves.

Link to comment
Share on other sites

Thanks for your comment.

Well I would say that its first feature is to be in native TypeScript and do not use interface.

It is very simple and has no ambition unless being useful for me and maybe for some other people.

About the terminology issue, I would be interested if you do not mind giving me a link about the spec proposal using the term Observables.

I have not used pub/sub since in my mind it is a little bit different (with a message broker iirc).

In the future, this library should implement 2-way data binding, that is the plan indeed, but for the moment, it is just an addEventListener()-like library, DOM independent.

Thanks for the feedback.

Link to comment
Share on other sites

https://github.com/tc39/proposal-observable this is the proposal, due to many libraries in userland already employing this spec (the spec is actually largely based on their work) it is gaining a fair amount of traction. RXJS has finally fallen in line with their latest implementation but there are other libraries like most.js that follow/define the spec pretty accurately whilst also maintaining performance (both libs have type defs). You might find the fantasy land spec interesting https://github.com/fantasyland/fantasy-land as its related.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...