JavaScript Development Space

The Best Way to Create Singleton Design Pattern in JavaScript and TypeScript

Add to your RSS feed22 July 20245 min read
The Best Way to Create Singleton Design Pattern in JavaScript and TypeScript

What is The Singleton Design Pattern?

The Singleton Design Pattern ensures that a class has only one instance and provides a global point of access to that instance. This pattern is useful when exactly one object is needed to coordinate actions across a system.

The Singleton Design Pattern is used for several key reasons in software development. Here are some of the main advantages and use cases for employing this pattern:

Advantages of Singleton

1. Controlled Access to Sole Instance

  • Ensures Single Instance: Guarantees that a class has only one instance and provides a global point of access to it.

  • Consistency: Helps maintain a single source of truth, preventing inconsistencies and conflicts that can arise from having multiple instances.

2. Resource Management

  • Resource Intensive: Ideal for managing resources such as database connections, file handles, or network connections, where multiple instances would be wasteful or problematic.

  • Improves Performance: Reduces the overhead associated with creating and destroying instances frequently.

Creating a Singleton pattern in JavaScript and TypeScript can be done in several ways. Here’s a detailed guide on how to implement a Singleton pattern effectively:

3. Global State Management

  • Centralized Configuration: Useful for managing global states or configurations that need to be consistent across the application. * State Sharing: Facilitates sharing of data or state across different parts of an application without the need to pass instances around.

4. Simplifies Code Structure

  • Encapsulation: Encapsulates the instantiation logic within the class, making the codebase easier to understand and maintain. * Avoids Global Variables: Reduces the need for global variables by providing a controlled access point.

5. Thread Safety

  • Concurrency Handling: In multi-threaded environments, the Singleton pattern can be designed to be thread-safe, ensuring that only one instance is created even when accessed concurrently.

Use Cases for Singleton Pattern:

1. Logging Services:

  • Ensure that there is a single logger instance that handles all log messages.

2. Configuration Management:

  • Manage application-wide configurations and settings from a single point.

3. Database Connections:

  • Maintain a single connection to a database, ensuring efficient resource usage.

4. Cache Management:

  • Implement a cache that is accessed globally and avoids the overhead of creating multiple cache instances.

5. Thread Pool Management:

  • Manage a pool of threads or worker objects, ensuring that the pool is created only once.

JavaScript Singleton

Using a ES6 classes:

js
1 class Singleton {
2 constructor() {
3 if (!Singleton._instance) {
4 Singleton._instance = this;
5 }
6 return Singleton._instance;
7 this.data = 'Hello from Singleton';
8 }
9
10 getData() {
11 return this.data;
12 }
13 }
14
15 const instance1 = new Singleton();
16 const instance2 = new Singleton();
17
18 console.log(instance1 === instance2); // true

Using a Closure:

js
1 const Singleton = (function () {
2 let _instance;
3
4 function createInstance() {
5 const object = new Object('Hello from Singleton');
6 return object;
7 }
8
9 return {
10 getInstance: function () {
11 if (!_instance) {
12 _instance = createInstance();
13 }
14 return _instance;
15 },
16 };
17 })();
18
19 const instance1 = Singleton.getInstance();
20 const instance2 = Singleton.getInstance();
21
22 console.log(instance1 === instance2); // true

TypeScript Singleton

Using a TypeScript Class with a Static Property:

ts
1 class Singleton {
2 private static _instance: Singleton;
3
4 private constructor(args: string[]) {
5 // Private constructor to prevent instantiation
6 }
7
8 public someMethod(): string {
9 return 'I am the instance';
10 }
11
12 public static getInstance(args: string[]): Singleton {
13 if (this._instance) {
14 return this._instance;
15 }
16
17 this._instance = new Singleton(args);
18 return this._instance;
19 }
20 }
21
22 const instance1 = Singleton.getInstance();
23 const instance2 = Singleton.getInstance();
24
25 console.log(instance1 === instance2); // true

Key Points:

  • Singleton Pattern ensures that a class has only one instance and provides a global point of access to it.

  • In TypeScript, you can use the private constructor and static methods to enforce the Singleton pattern.

  • For JavaScript, closures and ES6 classes provide a straightforward way to implement this pattern.

Singleton in older versions of JavaScript

In older versions of JavaScript, you can create a singleton by using an Immediately Invoked Function Expression (IIFE). This pattern leverages closures to create a single instance of an object while keeping private variables and methods out of the global scope.

Here’s how you can create a singleton in ES5-style JavaScript:

js
1 var Singleton = (function () {
2 // Private variable to hold the instance
3 var instance;
4
5 // Private method and properties
6 function createInstance() {
7 var obj = new Object("I am the singleton instance!");
8 return obj;
9 }
10
11 // Public API for Singleton
12 return {
13 getInstance: function () {
14 if (!instance) {
15 instance = createInstance(); // Create the instance only once
16 }
17 return instance;
18 }
19 };
20 })();
21
22 // Usage:
23 var singleton1 = Singleton.getInstance();
24 var singleton2 = Singleton.getInstance();
25
26 console.log(singleton1 === singleton2); // Output: true
27 console.log(singleton1); // Output: I am the singleton instance!

Explanation

-IIFE: The (function () { ... })() syntax allows us to create an immediately invoked function that runs once and returns an object containing the getInstance method. This method provides controlled access to the singleton instance.

  • Private instance Variable: Since the instance variable is within the closure of the IIFE, it’s private and can only be accessed through getInstance().
  • Controlled Instance Creation: getInstance() checks if instance is undefined. If it is, it calls createInstance() to create and assign the singleton instance; otherwise, it simply returns the existing instance.

This approach ensures that only one instance of the singleton is created, and any subsequent calls will return the same instance.

Example Use Case: Database Connection Pool

ts
1 class DatabaseConnection {
2 private static _instance: DatabaseConnection;
3
4 private constructor(args: string[]) {
5 // Initialize connection
6 }
7
8 public connect() {
9 // Connect to the database
10 console.log('Database connected');
11 }
12
13 public static getInstance(args: string[]): DatabaseConnection {
14 if (this._instance) {
15 return this._instance;
16 }
17
18 this._instance = new DatabaseConnection(args);
19 return this._instance;
20 }
21 }
22
23 // Usage
24 const db1 = DatabaseConnection.getInstance();
25 const db2 = DatabaseConnection.getInstance();
26
27 db1.connect();
28 console.log(db1 === db2); // true, same instance

Considerations:

  • Overuse Risk: Overusing Singleton can lead to tightly coupled code and make unit testing difficult.

  • Testing Challenges: Singletons can be hard to test due to their global state and potential side effects.

In summary, the Singleton pattern is powerful for managing global states and resources, ensuring consistency and efficient resource management across an application. However, it should be used judiciously to avoid issues related to maintainability and testability.

JavaScript Development Space

© 2025 JavaScript Development Space - Master JS and NodeJS. All rights reserved.