DEV Community

José Miguel Álvarez Vañó
José Miguel Álvarez Vañó

Posted on • Updated on • Originally published at jmalvarez.dev

Singleton pattern in TypeScript

Introduction

The singleton pattern is a creational pattern that allows you to make sure that only one instance of a class is created.

Applicability

Use the Singleton pattern when:

  • it's important that your application only creates an instance of a class.
  • the same instance of a class should be use globally.

Implementation

You can find the full example source code here.

1. Declare a static property to hold the instance of the class. Remember that static properties are shared across all instances of a class.

In the example I'm going to apply the Singleton pattern to a class that represents a database connection.

class Database {
  private static instance: Database;
}
Enter fullscreen mode Exit fullscreen mode

2. Make the constructor of the class private so that it cannot be called from outside the class.

For the example, the constructor won't have any parameters. In a real-world scenario, you might want to pass some configuration to the constructor.

class Database {
  private static instance: Database;

  private constructor() {}
}
Enter fullscreen mode Exit fullscreen mode

3. Declare a static method that will be used to get the Singleton instance of the class. When the method is called for the first time,
it will create an instance of the class and store it in the static property. On subsequent calls, it will return the instance stored in the static property.

class Database {
  private static instance: Database;

  private constructor() {}

  public static getInstance(): Database {
    if (!Database.instance) {
      Database.instance = new Database();
    }

    return Database.instance;
  }
}
Enter fullscreen mode Exit fullscreen mode

4. The singleton instance can now be used from anywhere in the application. But first, I'll ad some business logic for the example.

class Database {
  private static instance: Database;

  private constructor() {}

  public static getInstance(): Database {
    if (!Database.instance) {
      Database.instance = new Database();
    }

    return Database.instance;
  }

  public query(sql: string): void {
    console.log(`Querying database: ${sql}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

An example of how client code would use the Singleton instance:

const database = Database.getInstance();
database.query("SELECT * FROM users");

// Output:
// Querying database: SELECT * FROM users

const database2 = Database.getInstance();
if (database === database2) {
  console.log(
    "The same instance of Database was returned. The Singleton pattern works!"
  );
} else {
  console.log(
    "A new instance of Database was returned. The Singleton pattern failed."
  );
}

// Output:
// The same instance of Database was returned. The Singleton pattern works!
Enter fullscreen mode Exit fullscreen mode

Resources

Top comments (1)

Collapse
 
emmanuelonah profile image
Emmanuel Chikwendu Onah

I see that lots of typescript solutions for singleton makes the constructor private and add an extra static method(getInstance) but why?

Just to be clear, a design pattern is supposed to be a concept/idealogy to solution a re-occuring problem excluding an opinionated codification. That been said, we can still achieve singleton while keeping our conventional instantiation in please 👇:

/*Singleton es6*/
class Singleton {
private _name?: string;
private static instance?: Singleton;

constructor() {
if (Singleton.instance) return Singleton.instance;
Singleton.instance = this;
}

///
setName(name: string) {
this._name = name;
}

///
getName() {
return this._name;
}

}

const s1 = new Singleton();

s1.setName('Emmanuel');

const s2 = new Singleton();

s2.setName('Joseph');

console.log(s1 === s2, s1.getName(), s2.getName());


/*Singleton es5*/
function Singleton() {
// enforce instantiation
// or throw new Error("TypeError: Class constructor Singleton cannot be invoked without 'new'");
if (!new.target) return new Singleton();

///
if (Singleton.instance) return Singleton.instance;
Singleton.instance = this;

///
Singleton.prototype.setName = function setName(name: string) {
this._name = name;

};

///
Singleton.prototype.getName = function getName() {
return this._name;
};

}

const s1 = Singleton();

s1.setName('Emmanuel Onah');

const s2 = Singleton();

s2.setName('Jeremiah Joseph');

console.log(s1.getName(), s2.getName(), s1 === s2);


Enter fullscreen mode Exit fullscreen mode