Singleton design pattern. JS/TS Examples.
Tags: javascript, typescript, oop, designpatterns
Published: 2021-11-21 15:47:52

* [Implementation](#implementation)
* [Pros and Cons](#pros-and-cons)
* [Examples](#examples)

So why do we need to know design patterns?

First of all it helps you to save your time. Programming is not a new thing, a lot of problems have been already solved before. Lots of patterns and approaches have been invented and most of them are time tested. If you don't want to reinvent the wheel, you might be interested to know more about those preexisting patterns and approaches.
So *design patterns are typical solutions to commonly occurring problems in programming.*


In this short article we are going to cover the singleton design pattern. This pattern is a type of creational design patterns and probably one of the simplest ones.
&nbsp;

## Implementation <a name="implementation"></a>

*The Singleton pattern is just a way of creating a single object that is shared amongst a bunch of different resources throughout your application without having to recreate that object or losing the information inside of it.*


**1. It ensures that there is only one instance of a given class**
For example we may create a logger class which prints logs and keeps them inside the class. Following this patters you have to have a single instance of the logger which prevents losing/overwriting the logs list.


**2. This pattern also provides a way to access the single instance globally**
Going back to our logger class, it's pretty much obvious that we need it to be accessible from any file in our project. Because errors may appear anywhere and we want to log them.

&nbsp;
## Pros and Cons <a name="pros-and-cons"></a>

Pros (Global variables vs Singleton):

- Comparing to global variables, Singletons can not be modified(speaking of `var` in JS).
- Unlike global variables it doesn't exist until instantiated.
- Another advantage is that you are absolutely sure of the number of instances.
- You can manage the state of that instance.


Cons of using this design pattern:

- Someday when you have a lot of parts of your app rely on that Singleton obj it may became hard to change Singleton obj itself.
- As we already know Singleton's methods could be called from different parts of your app simultaneously at the same time which may cause a data/variables within this object to be overwritten/read incorrectly.  

FYI: there are more cons actually, but we're not going to cover all of them in this article.

&nbsp;
## Examples <a name="examples"></a>

JS Example:
  ```javascript
class SingletonLogger {
  // prevent modifying the instance property,
  // for example set it to null to create a second instance.
  static #instance;
  // prevent modifying/overwriting logs array.
  #logs = [];

  // classes in JavaScript...
  //    may not have a private field named '#constructor'
  // so it's not possible to disable...
  //    calling constructor() {} in JS.
  // if you try new SingletonLogger()...
  //    with private constructor in TS it will throw an error
  constructor() {
    if (SingletonLogger.#instance) {
      throw new Error('Can not instantiate second singleton');
    }
    SingletonLogger.#instance = this;
  }

  // getInstance should be static...
  //    to be able to call SingletonLogger.getInstance()
  static getInstance() {
    if (!SingletonLogger.#instance) {
      SingletonLogger.#instance = new SingletonLogger();
    }
    return SingletonLogger.#instance;
  }

  log(error) {
    this.#logs.push(error);
    console.log(error);
  }

  // since #logs array is private, we need to create a getter
  get logsArray() {
    return this.#logs;
  }
}

// Usage:
const logger = SingletonLogger.getInstance();
try {
  throw new Error('first err');
} catch(err) {
  logger.log(err); // log: first err
}
console.log(logger.logsArray); // log: [first err]

const logger2 = SingletonLogger.getInstance();
try {
  throw new Error('second err');
} catch(err) {
  logger2.log(err); // log: second err
}
console.log(logger2.logsArray); // log: [first err, second err]

// const logger3 = new SingletonLogger();
// Error: Can not instantiate second singleton

```

TS example:
  ```typescript
class SingletonLogger {
    private static instance: SingletonLogger;
    private logs: Array<Error> = [];

    private constructor() { }

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

    log(error: Error) {
        this.logs.push(error);
        console.log(error);
    }

    get logsArray() {
        return this.logs;
    }
}

// Usage:
// const singleton = new SingletonLogger(); // ...
//    TS won't let you do this
// Constructor of class 'SingletonLogger' is private...
//    and only accessible within the class declaration.

const singleton = SingletonLogger.getInstance();
try {
    throw new Error('first err');
} catch(err) {
    singleton.log(err as Error); // log: first err 
}
console.log(singleton.logsArray); // log: [first err] 

const sameSingleton = SingletonLogger.getInstance();
try {
    throw new Error('second err');
} catch(err) {
    sameSingleton.log(err as Error); // log: second err 
}
console.log(sameSingleton.logsArray); // log: [first err, second err] 
```

&nbsp;
### Conclusion
To be honest I haven't found any use cases in Front End development where singleton design pattern would be **really helpful**. Of course you can create the same logger as we did above or use the cache as a singleton object. But I personally think you will rarely have to use it.
Anyways, this design pattern is a part of [Gang of Four design patterns](https://en.wikipedia.org/wiki/Design_Patterns) and knowing all these patterns will take you to the next level as a dev.

Thanks for reading! Any feedback is appreciated!😊


> Photo by [Lucas Campoi](https://unsplash.com/@campoilucas)