How can the Singleton pattern be properly implemented in Typescript/ES6?

class Foo{

}

var instance: Foo;
export function getFooInstance(){
    /* logic */
}

or

export class Foo{
    private static _instance;
    private constructor(){};
    public getInstance(){/* logic */}
}

// Use it like this
Foo.getInstance()

I would like to ensure that only one instance of the object is created. Does anyone have any other suggestions besides this method?

Check out the Typescript Playground here.

Answer №1

In order to utilize the getter within the class, it must be declared static:

export class Bar{
    private static _instance;
    private constructor(){};
    public static getInstance(){/* implementation */}
}

It's worth noting that although the compiler enforces private visibility, in runtime it is still feasible to circumvent it, even inadvertently, such as when accessed directly from Javascript.

To ensure complete concealment, you can employ a module or namespace:

Module approach:

export interface IBar {}

class Bar implements IBar {}

var instance: Bar;
export function getBarInstance(): IBar {
    /* logic */

    return instance;
}

This code snippet introduces the IBar interface (which is also exported), ensuring that whoever obtains an instance is aware of the interface but not the actual class.

Namespace approach:

namespace Bar {
    export interface IBar {}

    class BarClass implements IBar {}

    const instance = new BarClass();
    export function getInstance(): IBar {
        return instance;
    }
}

Answer №2

When working with JS and TypeScript, if you are looking to have just one instance, consider enforcing it within the language itself by exporting an object literal.

const Singleton = {
  doSomething() {

  }
}

export default Singleton;

In my opinion, this adheres to the principle of KISS (Keep It Simple, Stupid), involves minimal boilerplate, and prevents the creation of multiple instances.

Alternatively, you can directly export functions as well. Remember that a module can act as a singleton for you.

export function doSomething() {
}

If you want to treat the imported module as an object, you can use the import *. However, I recommend the first approach if the functions truly belong to the object and are not all stateless static functions.

import * as Singleton from './Singleton';

Singleton.doSomething();

Answer №3

When deciding whether to allow the creation of a new instance for a singleton class, one must consider various factors. In some cases, the getInstance method can be excluded and the class constructor itself can function as a singleton factory:

class Foo {
    private static _instance;
    constructor(...args) {
        if (Foo._instance) {
            return Foo._instance;
        }

        // perform additional operations here if needed

        Foo._instance = this;
    };
}

A similar approach can also be applied using decorators for different classes, such as:

@singleton
class Foo { ... }

However, due to certain typing issues with TypeScript decorators as described here, it is recommended to implement custom inheritance logic within the singleton decorator instead of relying on Singleton extends Class:

function singleton(Class) {
    function extend(sub, sup) {

        for (var prop in sup)
            if (sup.hasOwnProperty(prop))
                sub[prop] = sup[prop];

        function __() {
            this.constructor = sub;
        }

        __.prototype = sup.prototype;
        sub.prototype = new __();
    };

    const Singleton = <any>function (...args) {
        if (Singleton._instance) {
            return Singleton._instance;
        }

        Class.apply(this, args);

        Singleton._instance = this;
    }

    extend(Singleton, Class);

    return Singleton;
}

Although this approach may impact typing, it helps maintain a clean syntax.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible to pass a different variable during the mouse down event when using Konva for 2D drawing?

I am trying to pass an additional value in a mouse event because my handleMouseDown function is located in another file. stage.on('mousedown', handleMouseDown(evt, stage)) Unfortunately, I encountered an error: - Argument of type 'void&apos ...

Modifying an HTML attribute dynamically in D3 by evaluating its existing value

I'm facing a seemingly simple task, but I can't quite crack it. My web page showcases multiple bar graphs, and I'm aiming to create a button that reveals or conceals certain bars. Specifically, when toggled, I want half of the bars to vanish ...

Error message "Unable to access property 'rotation' of an object that does not exist - Three.js"

I'm currently facing an issue where my code is executing as expected, but there are two errors popping up in the console saying "Cannot read property 'rotation' of undefined". It's puzzling because both variables are defined globally. I ...

Storing the subscription value retrieved from an API in a global variable

I am trying to find a way to make the data retrieved from an API accessible as a global variable in Typescript. I know that using subscribe() prevents this, so I'm looking for a workaround. Here is the API code: getResultCount(category:any):Obs ...

Should I specify each protected route in the middleware file in the matcher for NextJs 14?

Below is the middleware file I've implemented: import { NextResponse } from "next/server"; import { NextRequest } from "next/server"; import { getApiAuth } from "./app/middleware/api/auth"; const validateApi = (req: Requ ...

Utilize the functionality of the acuityscheduling API to streamline your

I've experimented with various methods but haven't had any success. Hopefully, you guys can share some insight. I'm utilizing acuityscheduling's API to fetch appointments. According to their documentation, the process should look someth ...

Understanding AngularJS and how to effectively pass parameters is essential for developers looking

Can anyone help me figure out how to properly pass the html element through my function while using AngularJS? It seems like this method works without AngularJS, but I'm having trouble with the "this" keyword getting confused. Does anyone know how I c ...

Ways to refine data using multiple criteria

I have a list of alarm data that I need to filter based on specific conditions. If there are multiple alarms of type "pull_Alarm" and "emergency_alarm" in the same location, I want to prioritize the "emergency_alarm". Here is my list: [ { ...

Split a string into chunks at every odd and even index

I have limited knowledge of javascript. When given the string "3005600008000", I need to devise a method that multiplies all the digits in the odd-numbered positions by 2 and those in the even-numbered positions by 1. This code snippet I drafted seems to ...

Trouble arises when trying to invoke a prototype function using setInterval

Having created a prototype class for Bot, I encountered an issue. Upon calling the init() function after its creation, it correctly alerts "a 5000". However, when the prototype function calls getUpdates(), it fails to reach the value of "this" and instead ...

The initial click may not gather all the information, but the subsequent click will capture all necessary data

Issue with button logging on second click instead of first, skipping object iteration function. I attempted using promises and async await on functions to solve this issue, but without success. // Button Code const btn = document.querySelector("button") ...

Tips on showcasing an array as a matrix with a neat line property

I am currently developing an application using TypeScript, and utilizing a JSON array structured like this: data = [{"name":"dog", "line":1}, {"name":"cet", "line":1}, ...

Exploration of jsTree capabilities

I am currently exploring the jsTree plugin API and I'm struggling to grasp where exactly the API functions like set_theme, show_dots, etc. should be applied. On this page, I noticed that some functions are preceded by jQuery, while others are precede ...

"Troubleshooting the lack of functionality in nested ajax calls, click events, or event

Initially, a navigation click event was created: $('#inner-navigation li a') .on('click', function (e) { e.preventDefault(); AjaxNavUrl.checkURL(this.hash); }); This event triggers an ajax call and respo ...

Tips for populating class attributes from an Angular model

Suppose there is a Class Vehicle with the following properties: public id: number; public modelId: number; public modelName: string; Now consider we have an object that looks like this {id: 1, modelId: 1, modelName: "4"} What is the best way to assign e ...

Changing synchronous functions to asynchronous

Attempting to transform synchronous calls into asynchronous ones using when...done. Despite being new at this, after extensive reading on the topic for the past two days, my code should work as intended. However, while it does function, it doesn't exe ...

Having trouble with Raphael's animation callback function?

It seems like I may not be using the callback feature correctly because when I run the code below, the "testCircle" doesn't animate before it disappears. var paper = Raphael(0, 0, 1280,600); var testCircle = paper.circle(300, 300, 50); test ...

A vertical line in Javascript extending upward from the base of an element

Is there a way to create a design where an infinite vertical line extends from the bottom of a circle (or in my case, a rectangle) without using css :after or pseudo-elements? I would like the line to be its own element and not limited by the restriction ...

function to choose in antd datepicker component

Is there a way to obtain the currently selected date within the onSelect function after updating the selected date in the state? onSelect = (cal) => { this.setState({ selectedValue: cal }); alert(this.state.selectedValue); After making ...

Eliminate duplicate time slots in Laravel and Vuejs

Currently, I am delving into laravel(5.2) and vuejs as a newcomer to both frameworks. My goal is to utilize vuejs to eliminate redundant time slots. In my blade file, the code looks like this: <div class="form-group"> <label for="form-fi ...