Invoking a function with the 'new' keyword

Recently, I've been considering converting one of my side projects to Typescript. However, I'm encountering a problem when it comes to calling functions using the 'new' keyword.

My issue arises when I try to call a function imported from another file in the following manner:

// Function in 'file.js'
function Foo() {
  this.x = 1;
  this.y = 2;
}
Foo.prototype.set = function() {
   return this.x + this.y;
};
export { Foo };
// Function in another file calling Foo
import { Foo } from './file';
function getSum() {
  let foo = new Foo(); // The error occurs at this line!!!
  foo.set();
}

Every time I attempt this, I receive the following error message:

'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.
.


After reviewing the typescript documentation, I realized that the call signature should be structured as shown below:

type SomeConstructor = {
  new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
  return new ctor("hello");
}

I attempted to apply this type structure to my 'Foo' function but struggled to implement it correctly.

// Function in 'file.js' --> renamed to 'file.tsx'
type FooType = {
  x: number,
  y: number,
};

type FooConstructor = {
  new (): FooType
};

function Foo(this: FooType) { // How do I add FooConstructor to this?
  this.x = 1;
  this.y = 2;
}
Foo.prototype.set = function(): number {
   return this.x + this.y;
};

Despite my efforts, I couldn't successfully integrate the construct signature while exporting/importing or during function calls. Every attempt resulted in errors.

export { Foo: FooConstructor };
import { Foo: FooConstructor } from './file';
let foo = new Foo() as FooConstructor;

Do I need to convert the Foo function into a class? Is that the only viable option for typing it correctly?! Many resources demonstrate how to type classes, but even with that approach, I received an error stating,

Type 'FooType' is not assignable to type 'FooConstructor'
.

I am feeling quite lost here and would appreciate any guidance!


EDIT: My File.ts now resembles the following structure:

To outline the declaration in the File.ts file, I modified it as follows:

type FooType = {
  x: number,
  y: number,
};

declare class Foo {
  constructor();
  x: number;
  y: number;
  setHeader(): number;
}

function Foo(this: FooType) {
  this.x = 1;
  this.y = 2;
}
Foo.prototype.set = function(): number {
   return this.x + this.y;
};

export { Foo };

Answer №1

To tackle this issue, the best approach was to transform the function below into a class:

function Foo(this: FooType) { // How do I add FooConstructor to this?
  this.x = 1;
  this.y = 2;
}
Foo.prototype.set = function(): number {
   return this.x + this.y;
};

into:

class Foo() {
  x: number;
  y: number;
  constructor() {
    this.x = 1;
    this.y = 2;
  }
  set (): number {
   return this.x + this.y;
  }
}

Answer №2

Officially, Typescript made the decision not to include support for this type of traditional "old school" JavaScript syntax. They opted for the class syntax instead.

Nevertheless, it is possible to make it work by utilizing code from a previous answer provided by me:

// This section contains only instance members.
export interface Circle { // Naming the interface same as the variable.
  radius: number;
  area: () => number;
  perimeter: () => number;
}

// You can define static members here.
interface CircleConstructor {
  new(radius: number): Circle;
}

export const Circle = function(this: Circle, radius: number) {
  const pi = 3.14;
  this.radius = radius;
  this.area = function () {
    return pi * radius * radius
  }
  this.perimeter = function () {
    return 2 * pi * radius;
  }
} as unknown /*as any*/ as CircleConstructor; // Notice the casting
    
const c = new Circle(3); // works fine

There are some issues with this approach, such as the use of the any type which is not recommended, and the reliance on the as operator which can be considered unsightly. To address this, I have switched to using unknown instead of any, although the main problem remains. This adjustment prevents linting errors, making it an improvement over the usage of any.

So far, this is the most effective solution I have discovered. The developers of Typescript are aware of this issue but have chosen not to provide support for this type of "syntax".

Answer №3

If you have older JavaScript code that requires typing for TypeScript, you can generate definition files containing only types while keeping the runtime code in JavaScript.

For instance, a file.js file with an ES5 Foo class could have a corresponding file.d.ts with the following content:

// file.d.ts
export declare class Foo {
  x: number;
  y: number;
  constructor();
  set(): number;
}

// Any other TypeScript file
import { Foo } from 'any/path/file';
let foo = new Foo(); // Foo
const num = foo.set(); // number

The error in the question was moving the old JavaScript code to a TypeScript file instead of creating a separate definition file as TypeScript does not directly support ES5 syntax when defining classes.

Note: The constructor(); in this example is redundant since it's the implicit default constructor. However, you can define the constructor with any arguments or types and utilize multiple signatures using function overloads.

TypeScript playground

To answer the broader question of "how to type a constructible function not written as an ES6 class?", you can utilize the new keyword in TypeScript type or interface:

interface Foo {
  x: number;
  y: number;
  set(): number;
}

type Constructor<T> = new (...args: any[]) => T;

declare const newableFunction: Constructor<Foo>;

const obj = new newableFunction(); // Foo

TypeScript playground

In case the implementation is a simple function, it should ideally be placed in a separate JavaScript file to avoid mismatch errors between the declaration and implementation types during compilation.

Furthermore, consider a hybrid function that can be invoked with or without the new keyword:

// Function in 'file.js'
function newableFunction() {

  const self = Object.getPrototypeOf(this) === newableFunction.prototype ? this : {};

  self.x = 1;
  self.y = 2;
  self.get = function () {
    return self.x + self.y;
  }
  return self;
}

This unique function can be typed as follows:

interface Foo {
  x: number;
  y: number;
  get(): number;
}

interface Constructor<T> {
  (...args: any[]): T
  new (...args: any[]): T
}

export const newableFunction: Constructor<Foo>;

Example usage:

import { newableFunction } from './file';

const foo1 = newableFunction(); // Foo
const foo2 = new newableFunction(); // Foo

This kind of pattern can also be seen in native JavaScript types like the Number constructor in lib.es5.d.ts:

interface NumberConstructor {
    new(value?: any): Number;
    (value?: any): number;
    // ...

Answer №5

In case you want to implement a similar scenario like this one, you can follow these steps - I will provide a more general example:

Imagine you have the following code snippet:

class Parent {
  constructor(name: string) {
    console.log(`Name is: ${name}`)
  }
  parent_method() { 
    console.log('Method in the parent class')
  }
}
class ChildOne extends Parent {
  constructor(name: string) {
    super(name)
  }
  child_one_method() {
    console.log('Child method in ChildOne class')
  }
}
class ChildTwo extends Parent {
  constructor(name: string) {
    super(name)
  }
  child_two_method() {
    console.log('Child method in ChildTwo class')
  }
}

type SomeConstructor<T extends Parent> = new (s: string)=> T

This approach allows you to create a function that initializes an instance for a generic type:

function initializer<T extends Parent>(ctor: SomeConstructor<T>, name: string) {
  return new ctor(name)
}

Instead of using fixed parameters as shown in the documentation example, here's a more flexible version of the generic initializer:

function fn_1(ctor: SomeConstructor<ChildOne>) {
  return new ctor("Child One");
}

Now, how do you use the initializer method? Here's an example of creating instances with specific parameter values:

const child_one = initializer(ChildOne, 'CHILD ONE')  // "Name is: CHILD ONE"
const child_two = initializer(ChildTwo, 'CHILD TWO')  // "Name is: CHILD TWO"

child_one.child_one_method()  // "Child method in ChildOne class"
child_two.child_two_method()  // "Child method in ChildTwo class" 
child_one.parent_method()  // "Method in the parent class" 

Note: This behavior resembles Java's Reflection, where you can instantiate any Generic Type with a specified parameter.


If you still prefer the implementation from the Docs, you can use this alternate method instead of initializer:

function fn_2(ctor: SomeConstructor<ChildTwo>) {
  return new ctor("Child Two");
}

And to utilize this method:

const two = fn_2(ChildTwo)  // "Name is: Child Two"
two.child_two_method()   //  "Child method in ChildTwo class" 

Important: This example is less generic compared to the previous one. Finally, you can test out this working example in the Playground

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

Verifying whether an item shows potential for being a successful function

When using protractor.js, I am working with functions that utilize promises/defer. For instance: var myFunc = function(_params) { var deferred = protractor.promise.defer(); /***do some magic code***/ /****wait for other promises****/ /*****deferr ...

Attaching an event listener to elements with a specified Class name

Currently facing a challenge where I am trying to implement a function that captures click events on squares. The objective is to capture the click event on every button with the square class. import { Component, OnInit } from '@angular/core&apos ...

Draggable resizing of the Accordion component in React.js using Material-UI

In the visual representation, there are two Accordions—one positioned on the left (30%) and the other on the right (70%). Upon clicking the button, the right accordion disappears, while the one on the left expands to cover the full width (100%). A featu ...

jQuery slideshow controls

Having some trouble making my slideshow slide left and right when a control is clicked. Here is the markup... Left Right <div class="clear">&l ...

Experiencing problems with the Locale setting when utilizing the formatNumber function in Angular's core functionalities

I am having trouble formatting a number in Angular using the formatNumber function from the Angular documentation. Here is my code snippet: import {formatNumber} from '@angular/common'; var testNumber = 123456.23; var x = formatNumber(Numb ...

Is it possible to achieve two-way data binding across various components in Angular 2?

Currently, I am facing an issue with two components in my Angular project - HomePageComponent and StudentResultsComponent. The problem arises when I type something in the input field of HomePageComponent; the value is not updating in the StudentResultsComp ...

Consider utilizing 'fetch' or alternative techniques in place of 'fs' when reading markdown files specifically within NextJS

I'm currently working on a blog that includes math formulas in markdown pages. The markdown files are stored locally in a folder. Here is the code snippet from my Blogpage.tsx file- import React from 'react' import fs from "fs" import Markd ...

JavaScript is utilized to update HTML content through an Ajax request, but unfortunately, the styling is

Apologies for the unconventional title, I am currently in the process of creating a website. When the page is initially loaded, I call upon two scripts within the 'head' tag to create an attractive dynamic button effect. <script src="https:// ...

Retain the previously selected option in a PHP combobox even when changing pages

Can someone please assist me with my PHP code? I am having trouble keeping my combobox unchanged after a page reload. It works fine until I change pages in my pagination. Can anyone help? THIS IS MY PHP. PHP ...

Issue with ThreeJS CSG when trying to intersect extruded geometries

element, I'm facing a challenge with extracting multiple views and intersecting them to form a final polygon. The issue arises when there are floating extra parts in the result that are unexpected. My goal is to find a solution to detect these extrane ...

Nested Javascript setInterval functions causing timer to increment continuously

I've encountered a situation where an ajax request is refreshing a page using setInterval every 5 seconds. Within that ajax request, there's another setInterval function that blinks a certain element every half-second based on a condition. The ...

Developing a declaration for an unnamed function in a JavaScript file

module.exports = function (argument1, argument2) { return { myFunction } function myFunction () { ... } } What is the process for creating a TypeScript declaration file for this specific JavaScript file? ...

Processing the results from a recursive function call in Node.js

I have an objects array that needs to be processed with delayed calls to an external server, then collect all results into an array for postprocessing. Here is an example: (async () => { const serverOperation = () => new Promise(resolve => setT ...

Dealing with adding up optional values from v-model in Vue.js can be a challenging task

Within this function, I need to display the remaining amount. remainingAmount: function() { return parseFloat(this.sumAmount) - (parseFloat(this.cash) + parseFloat(this.kNet) + parseFloat(this.kNetOnline)); } The three parameters cash ...

Is it possible to retrieve a section of a string?

Currently, I am dealing with a string that has the following format: item_questions_attributes_abc123_id My main goal is to extract the abc123 portion specifically. This segment of the string can consist of any alphanumeric characters in either upper or ...

Make sure to remain in the same division area when refreshing the page

Whenever I click the next button and move to step 2, I want the page to stay on the same div even after reloading. The two divs used are join_form_1 and join_form_2 Currently, when I reload the page, it reverts back to the first div. Is there a way to e ...

JavaScript - Receiving alert - AC_RunActiveContent.js needed for this page to function

When I attempt to open the HTML file in my application, a pop-up message appears stating that "This page requires AC_RunActiveContent.js." However, I have already imported and referenced the AC_RunActiveContent.js file in my package. Can someone assist m ...

Encountering a 403 error while trying to deploy a Node.js application on Heroku

Yesterday, I encountered an issue while trying to access a Node.js application on Heroku. The error message from the Chrome console was: Refused to load the image 'https://browser-rpg-app.herokuapp.com/favicon.ico' due to Content Security Policy ...

I am looking to initiate the animation by triggering the keyframe upon clicking a button

Is there a way to create an animation triggered by a button click instead of loading the page? Each table data cell has its own class with a unique keyframe defining the style changes over time. @-webkit-keyframes fadeIn { from { opacity:0; ...

Guide on establishing a connection between PHP web server and Socket.io on a node.js server

I have setup a websocket on server 10.0.4.18 at port 8020 using node.js version 4.0. The implementation includes socket.io v1.3, express, and express-session. Project Definition The goal is to establish a session in socket.io for each user connecting fro ...