DEV Community

Thales Bruno
Thales Bruno

Posted on

Singleton classes with Typescript

In this post, we will learn how to create a singleton class using Typescript.

Singleton pattern

The Singleton design pattern is a very well-known pattern used in software engineering that ensures we only have one instance, or a single instance, of a class in our application and also provides an easy way to get access to that object.

Implementation

In order to implement a Singleton class we basically need to follow these two steps:

  1. Make the class constructor private, preventing from using the new operator to create objects of that class.
  2. Create a static method to instantiate our single instance and make it accessible through the application.

Here's a Lonely class which implements the singleton pattern:

class Lonely {
  private static instance: Lonely;

  private constructor() {}

  static getInstance() {
    if (this.instance) {
      return this.instance;
    }
    this.instance = new Lonely();
    return this.instance;
  }
}
Enter fullscreen mode Exit fullscreen mode

Breaking down the code:

First, we define an instance property:

private static instance: Lonely;
Enter fullscreen mode Exit fullscreen mode

This property holds the single instance of our Lonely class. It's private and static since it shouldn't be accessible from its objects (or from its only object in our singleton case).

Then, we have the constructor:

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

This is one of the key pieces where we make the constructor private, so if we try to instantiate new object of Lonely with const newInstance = new Lonely() we get an error: Constructor of class 'Lonely' is private and only accessible within the class declaration.

And finally, we have the getInstance method.

  static getInstance() {
    if (this.instance) {
      return this.instance;
    }
    this.instance = new Lonely();
    return this.instance;
  }
Enter fullscreen mode Exit fullscreen mode

We don't need to name it getInstance but it's a good practice following the naming convention so it helps our code to be widely understandable.

What we must do is make it static and public, since it will be the point of access to creating the single instance of our class.

The logic is pretty simple: if we already have an instance of our class we just return it; if it's the first instantiation then we create our object calling the private constructor new Lonely() and return it.

We could also use the Lonely class name instead of the this keyword:

  static getInstance() {
    if (Lonely.instance) {
      return Lonely.instance;
    }
    Lonely.instance = new Lonely();
    return Lonely.instance;
  }
Enter fullscreen mode Exit fullscreen mode

Accessing our single instance

To access the only object of our Lonely class we just need to call its getInstance method:

const l1 = Lonely.getInstance();
const l2 = Lonely.getInstance();

console.log(l1 === l2); // return true
Enter fullscreen mode Exit fullscreen mode

Top comments (6)

Collapse
 
blu3 profile image
Lluís Marquès i Peñaranda • Edited

we've been trying to get rid of this pattern since it was invented. Please just don't use it unless you are connecting to a data source (i.e: database) and need to reuse the same connection or you are implementing some sort of NullObject pattern where it makes sense to reuse that unique instance that always behaves the same way.
Singleton pattern has really niche use cases and is usually a rotting smell of poorly designed code due to ofuscating dependencies, difficulting test cases and other headaches.

Collapse
 
thalesbruno profile image
Thales Bruno

Appreciate your heads up Lluís! Indeed, as with any other pattern or almost everything in software engineering, the problem is in the way they are being used, misused/abused rather. Agree that it has really niche use cases :)

Collapse
 
herve07h22 profile image
Herve
// Shorter way in 1 line :
export const lonely = new Lonely();
Enter fullscreen mode Exit fullscreen mode

Accessing our single instance :

import { lonely as l1 } from "./lonely";
import { lonely as l2 } from "./lonely";
console.log(l1 === l2); // return true
Enter fullscreen mode Exit fullscreen mode
Collapse
 
devjoco profile image
Devon

Is this really a "1 line" way of doing the same?
Where is the definition of Lonely?
What prevents another class from creating a second instance of Lonely with its own call to new Lonely();?

Collapse
 
vantanev profile image
Ivan Tanev

In this pattern, you never export the Lonely class. The definition is omitted in the original example as well.

The problem with this pattern is the so-called Dual Package Hazard, so the pattern is best avoided in library code. github.com/GeoffreyBooth/dual-pack...

Collapse
 
amatiasq profile image
A. Matías Quezada

How is that better than

const lonely = {
 myMethod() {}
};
Enter fullscreen mode Exit fullscreen mode

or if you want to have private state

const lonely = new class {
  #myPrivate;
  constructor() {}
  myMethod() {}
};
Enter fullscreen mode Exit fullscreen mode

Having a type is just typeof

type Lonely = typeof lonely;
Enter fullscreen mode Exit fullscreen mode