Determining the instance type of a TypeScript singleton class

I have a unique singleton implementation:

class UniqueSingleton {
  private static instance: UniqueSingleton;

  private constructor() {
    // Only allows instantiation within the class
  }

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

  someMethod(): void {
    console.log("Singleton method executed");
  }
}

Although there is a type InstanceType, it does not support private constructors.

// Attempting to assign a private constructor type to a public constructor type results in an error
type SingletonType = InstanceType<typeof UniqueSingleton>;

How can a custom type be created to retrieve the instance type of classes with private constructors?


UPDATE

Let me clarify the situation. I am working with a unique singleton class that has a private constructor. I need to pass this singleton to the constructor of another generic class as a parameter:

class CustomEntity<T extends typeof UniqueSingleton = typeof UniqueSingleton> {
    private singletonInstance: InstanceType<T>;
    
    constructor(instance: InstanceType<T>) {        
        this.singletonInstance = instance;        
    }
}

Due to the UniqueSingleton class having a private constructor, I encounter the error

Cannot assign a 'private' constructor type to a 'public' constructor type
when trying to access its instance InstanceType<T>. Therefore, I am inquiring if it is feasible to create a custom generic type that supports classes with "private" constructors and returns a similar instance type to "InstanceType"?

Answer №1

According to TypeScript, class constructors are assumed to have a prototype property that has the same type as the class instance. In reality, this assumption is not always accurate because in most cases, class fields are not actually part of the prototype. Despite this, TypeScript intentionally maintains this approximation and has rejected proposals to change it (refer to microsoft/TypeScript#11558 and microsoft/TypeScript#20922 for more information).

If you cannot access the InstanceType<T> utility type due to a private constructor, you can still retrieve the same information by using indexing to access the constructor's type with "prototype":

type NewInstanceType<T extends { prototype: any }> =
  T["prototype"];

type UniqueType = NewInstanceType<typeof Unique>
// type UniqueType = Unique

class Bar { b = 2 }
type Sample = NewInstanceType<typeof Bar>
// type Sample = Bar;

This allows you to define your MainEntity class in this manner:

class MainEntity<T extends typeof Unique = typeof Unique> {
  private uniqueInstance: NewInstanceType<T>;

  constructor(instance: NewInstanceType<T>) {
    this.uniqueInstance = instance;
  }
}

For a specific case like Unique, you can use

ReturnType<typeof Unique.getInstance>
with the ReturnType<T> utility type:

type NewUniqueType = ReturnType<typeof Unique.getInstance>;
// type NewUniqueType = Unique

type UniqueInstance<T extends typeof Unique> = ReturnType<T["getInstance"]>;
type NewUniqueInstanceType = UniqueInstance<typeof Unique>;
// type NewUniqueInstanceType = Unique

However, using T["prototype"] provides more flexibility in certain situations.

Link to Playground for code testing

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

What is the best way to set up TypeScript interfaces using predefined string literals to limit the possible data types for shared attributes?

In this scenario, we have two interfaces named A and B with validation and variant properties. The goal is to create an Example object by using only the variant and validation values provided (since field is already defined). However, I encountered an erro ...

Exploring the inner workings of the canDeactivate guard feature in Angular

Exploring the concept of guards in Angular has sparked a question in my mind. Why can't we simply have a class with a deactivate method that we can import and use as needed? The provided code snippets illustrate my confusion. export interface CanComp ...

What are some examples of utilizing paths within the tsconfig.json file?

Exploring the concept of path-mapping within the tsconfig.json file led me to the idea of utilizing it to streamline cumbersome path references: https://i.sstatic.net/AYmv4.png The project layout is unconventional due to its placement in a mono-repositor ...

Troubleshooting problems with resolving deeply nested promises

My approach to utilizing promises has been effective until now. The issue arises when the console.log(this.recipe) returns undefined and console.log(JSON.stringify(recipes)) displays an empty array. This suggests that the nested promises may not be resolvi ...

Choose a value, then multiply it by a number using a reactive

I have been attempting to multiply a fixed value by the selected value of a mat-select element, for example A x B, where A remains constant and does not change while B is the changing value from the mat-select. After performing this multiplication, I aim ...

Issue with TypeScript error encountered when attempting to save data locally using React-Redux and LocalStorage

Currently, I am delving into the worlds of React-Redux and TypeScript. Within my small app, I aim to utilize localStorage to store data locally. I attempted to address this based on information from this particular answer, but ran into a TypeScript erro ...

Disarrayed generic parameters in TypeScript

The title of the question may not be perfect, but it's the best I could do. Imagine a scenario where there is a function that accepts a callback along with an optional array. The callback takes an index and another optional array as parameters, and t ...

Is it feasible to obtain the userId or userInfo from the Firebase authentication API without requiring a login?

Is it feasible to retrieve the user id from Firebase authentication API "email/password method" without logging in? Imagine a function that takes an email as a parameter and returns the firebase userId. getId(email){ //this is just an example return t ...

Ensure all fields in an interface are nullable when using TypeScript

Is it possible to create type constraints in TypeScript that ensure all fields in an interface have a type of null? For example, if I want to write a validation function that replaces all false values with null, how can I achieve this? interface y { ...

Understanding how the context of an Angular2 component interacts within a jQuery timepicker method

Scenario: I am developing a time picker component for Angular 2. I need to pass values from Angular 2 Components to the jQuery timepicker in order to set parameters like minTime and maxTime. Below is the code snippet: export class TimePicker{ @Input() ...

What are the steps for utilizing the useReducer Hook with TypeScript?

I have successfully converted a React app to Typescript, but I am facing an issue with the useReducer Hook. The error message I'm getting is preventing me from moving forward. I have attempted different approaches to passing TypeScript interfaces in ...

Is it possible to close a tab while the chrome extension popup is still active?

I am currently developing a Chrome extension that reads the content of the current tab and performs a heavy task on the backend. If I were to close the tab while the process is ongoing, how can I allow the user to do so without waiting for the task to fi ...

How can you display a set of components in React using TypeScript?

I'm currently working on rendering an array of JSX Components. I've identified two possible methods to achieve this. Method one (current approach): Create the array as an object type that stores the component properties and then build the JSX co ...

Binding objects and properties from Angular2/Typescript to a template

Disclaimer: Seeking insight on the correct approach or any additional information _____________________________________________________________________ ANGULAR1 When working with angular1, we had the option to define our objects in the following ma ...

Encountered a problem when implementing flowbite in a project using NextJS and TypeScript

I recently added tailwind and flowbite to my NextJS project. After importing "flowbite" in the _app.tsx file, I encountered the following error message: ReferenceError: document is not defined at Object.366 (D:\shopflo\next-tailwin ...

Automatically shift focus to the next input when reaching the maximum length in Angular

Looking for a smoother way to focus the next input element in Angular without manually specifying which one. Here's my current HTML setup... <div class="mb-2 digit-insert d-flex align-items-center"> <div class="confirmation-group d-flex"&g ...

Dealing with JSON data in the format of `(Object object)` requires a specific approach

I originally encountered object object when attempting to display JSON API data in HTML. I then used keyvalue in *ngFor which allowed me to display the object, but I am wondering how I can access and display the entire JSON data? Here are the relevant cod ...

Is there a workaround in TypeScript to add extra details to a route?

Typically, I include some settings in my route. For instance: .when('Products', { templateUrl: 'App/Products.html', settings: { showbuy: true, showex ...

After restoring my Windows system, I encountered an issue with locating the typescript.js module for my Angular app development. I am currently troubleshooting the build

My PC had some issues, so I decided to restore Windows to an older restoration point. However, after doing this, I encountered an error while trying to build my Angular App: C:\Udemy\AngularDeCeroAExpertoEdicion2021\03-paisesApp>npm run ...

Ensure that the string functions as the primary interface

I'm working with an interface that looks like this interface Cat { color: string, weight: number, cute: Boolean, // even though all cats are cute! } Currently, I need to accomplish something similar to this const kitten: Cat = ... Object. ...