"Building" within TypeScript interface

After receiving the interface below from a library I'm utilizing, I am struggling to create an implementation:

export interface LatLng {
  constructor(lat: number, lng: number): void;
  lat(): number;
  lng(): number;
}

I need to generate a testing mock for this class, but my attempt at a natural-looking implementation with defined constructor failed:

export class LatLngImpl implements LatLng {
  constructor(private _lat: number, private _lng: number) {  }

This resulted in a compilation error:

Class 'LatLngImpl' incorrectly implements interface 'LatLng'. Types of property 'constructor' are incompatible. Type 'Function' is not assignable to type '(lat: number, lng: number) => >void'. Type 'Function' provides no match for the signature '(lat: number, lng: >number): void'

I have come across information about constructor interfaces in TypeScript, but I don't believe it's relevant in this scenario.

Edit:

My confusion lies in the declaration of constructor() within the interface. Typically, interfaces with constructor signatures use the syntax new ().

Answer №1

Do you know the origin of this library? The individual responsible for writing it needs to be addressed. It seems like a mistake (given that they never tested it) or perhaps intentionally malicious: using the almost-reserved word constructor as an identifier for an instance method is simply inviting trouble.

UPDATE 2019-04-25: The consequences go beyond just being a questionable idea; it appears that with forthcoming native support in JavaScript for class fields, having a property named "constructor" within a class will become an error. TypeScript is set to adjust for this starting from version 3.5 onwards, rendering the current implementation outdated. Refer to the recent GitHub issue Microsoft/TypeScript#31020 for more details. Post-TS3.5, having a class with an instance constructor property that isn't the actual constructor function itself might not be viable.

The author of the library likely either meant to reference a genuine constructor callable via new, in which case they should consider something akin to @Series0ne's recommendation; or, if the intention was truly to employ an instance method for object initialization, choosing a conventional method name such as init() would be more appropriate.

In any scenario, accepting the provided interface and especially implementing it is ill-advised.

Lets proceed with an attempt at implementation:

export class LatLngImpl implements LatLng {
  private _lat: number;
  private _lng: number;
  lat() {
    return this._lat;
  }
  lng() {
    return this._lng;
  }
  // crucial section, though refer to Microsoft/TypeScript#31020
  "constructor"(lat: number, lng: number) { 
    this._lat = lat;
    this._lng = lng;
  }
}

The workaround involves utilizing the string literal "constructor" instead of the bare word constructor. According to the TypeScript specification:

String literals can be employed to assign non-standard identifiers to properties

By opting for the string literal, we manage to define it as an instance method rather than the static class constructor method invoked during new calls, facilitating smooth compilation.

We can now utilize it, should we dare:

const latLng = new LatLngImpl();
latLng.constructor(47.6391132, -122.1284311); // 😠 Why not init()?
console.log("Latitude: " + latLng.lat() + ", Longitude: " + latLng.lng());

Regrettably, daring stands mistaken.

REVISITING 2019-04-25 The aforementioned quoted-string implementation will no longer be operational post TypeScript 3.5, with the correct response being "this cannot be done" and "opt for a real constructor instead".

Hoping this clarifies matters; best of luck!

Answer №2

When it comes to constructors, they are essentially special static function calls that return an instance of themselves. This is why it doesn't quite fit to include them as part of an interface, as interface members are typically bound to instances.

In Typescript, there's a clever compiler workaround to make constructors statically bound and it leverages this technique for ambient declarations.

To provide an implementation in your scenario, you'll need to exclude the constructor from the interface like so:

interface LatLng {
    lat(): number;
    lng(): number;
}

class LatLngImpl implements LatLng {
    constructor(lat: number, lng: number) {

    }

    lat(): number {
        return 0;
    }

    lng(): number {
        return 0;
    }
}

If the LatLng interface is already implemented elsewhere, simply provide an ambient declaration like this:

interface LatLng {
    lat(): number;
    lng(): number;
}

interface LatLngConstructor {
    new(lat: number, lng: number): LatLng;
}

declare var LatLng: LatLngConstructor;

It's worth noting that LatLngConstructor defines a new(...): LatLng, which outlines the signature of the constructor.

Answer №3

In my opinion, it is not possible to include a constructor inside an interface.

interface IPerson 
{
    name: string;
}

class Person implements IPerson
{
    constructor(public name: string) {
       //TODO
    }
}

var personA: IPerson = new Person('Alice');
var personB: IPerson = { name: 'Bob' };

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

Angular Error Handler - Extracting component context information from errors

In the event of a TypeScript error, I am looking to send all of the component's attribute values to a REST API. It would greatly benefit me if I could access the component's context (this) in order to retrieve the values of all attributes within ...

Leveraging Java and TypeScript code for specific functionalities within an Ionic 2 Android application

When it comes to creating hybrid apps that support Windows, iOS, and Android simultaneously using web technologies such as Html, CSS, and Js, Ionic is the go-to choice. However, there may be certain features not supported by the Ionic 2 framework. Is it ...

Typescript's way of mocking fetch for testing purposes

I have a query regarding the following code snippet: import useCountry from './useCountry'; import { renderHook } from '@testing-library/react-hooks'; import { enableFetchMocks } from 'jest-fetch-mock'; enableFetchMocks(); i ...

Issue: Unable to locate a matching object '[object Object]' of type 'object'. NgFor can solely bind to data structures like Arrays and Iterables

I am facing an error that says "Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays." I am trying to create a Notification list but I can't figure out w ...

Tips for making use of incomplete types

Is there a way to reference a type in TypeScript without including all of its required fields, without creating a new interface or making all fields optional? Consider the following scenario: interface Test { one: string; two: string; } _.findWhe ...

Utilize a fresh function in Angular to retrieve and store data from a URL into a variable

Currently, I am attempting to utilize Angular in order to retrieve data from a link upon clicking a button. As a newcomer to Angular with only 2 days experience, my knowledge is quite limited. What I aim to achieve is triggering the loading of JSON data w ...

Start Transloco in Angular before the application begins

For our Angular project, we have implemented Transloco to handle translations. Within my typescript code, I am using the transloco service in this manner: this.translocoService.translate('foo.bar') I understand that it is crucial to ensure that ...

Angular 5 - capturing form inputs - activating event upon selecting suggested values

When I click on suggested values below the input field, the (click)="doSomething()" event doesn't fire. How do I handle this issue? I would like to be able to type something in the input field and then have an event triggered when clicking on the su ...

Enhance Angular Forms: Style Readonly Fields in Grey and Validate Data Retrieval using Typescript

Is there a way to disable a form and make it greyed out, while still being able to access the values? https://i.sstatic.net/wt2c3.png 1/ Disabling controls with this.roleForm.controls['id'].disable() in Typescript does not allow me to retrieve ...

Typescript is failing to return nested types when attempting to return a nested object

My goal is for my function to return a nested type of Content, but it's not working even though the type that should be returned is known. Let's take a look at an example: type Content = { some: { extra: string; prop: number; ...

Changing the name of an Angular2 import module

Deciding to improve readability, I opted to rename the @angular module to angular2 and gain a better understanding of how imports function. Prior to making this change, the systemjs.config.js appeared like so: (function(global) { var map = { ...

After extraction from local storage, the type assertion is failing to work properly

I have a unique situation in my Angular project where I have stored an array of data points in the local storage. To handle this data, I have created a custom class as follows: export class Datapoint { id: number; name: string; // ... additional pr ...

Angular: failure to update a specific portion of the view

I'm currently working on a directive template that features the following code snippet: <div class="colorpicker"> <div>Chosen color</div> <div class="color_swatch" style="background-color: {{ngModel}}">&nbsp;</div> & ...

Error message: While running in JEST, the airtable code encountered a TypeError stating that it cannot read the 'bind' property of an

Encountered an error while running my Jest tests where there was an issue with importing Airtable TypeError: Cannot read property 'bind' of undefined > 1 | import AirtableAPI from 'airtable' | ^ at Object.&l ...

The type '{ domain: any; domainDispatch: React.Dispatch<any>; }' cannot be assigned to a type 'string'

Within my codebase, I am encountering an issue with a small file structured as follows: import React, { createContext, useContext, useReducer } from 'react' const initState = '' const DomainContext = createContext(initState) export co ...

The issue persists in VSCode where the closing brackets are not being automatically added despite

Recently, I've noticed that my vscode editor is failing to automatically add closing brackets/parenthesis as it should. This issue only started happening recently. I'm curious if anyone else out there has encountered this problem with their globa ...

Waiting for the response from $http in Angular2

In almost all REST requests, I require the user's ID to be included. After logging in a user, I store the token in local storage and pass the ID to user.service.ts (using the function setUserId(userId);). However, when authenticating a user using onl ...

When converting to a React Functional Component using Typescript, an error occurred: The property 'forceUpdateHandler' could not be found on the type 'MutableRefObject<Spinner | null>'

Looking to convert the App component in this CodePen into a Functional component using Typescript. Encountering an error when attempting to run it: ERROR in src/App.tsx:13:14 TS2339: Property 'forceUpdateHandler' does not exist on type 'Mu ...

Is it considered best practice to pass an argument that represents an injectable service?

Is it a good practice to pass an argument that is an injectable service to a function? Hello everyone, I have been doing some research but have not found a definitive answer to the question above yet. I am currently working with Angular and came across so ...

Optimal method for consecutively making N number of API calls in Angular 9 in a synchronous manner

Having a service method for API calls structured as follows: getUsers(id){ return this.http.get(`${env.apiURL}/id`) } Now, the requirement is to call this method for a list of users stored in an array: userId=[1,2,3,4,5,6,7,8,9] The goal is to retrieve ...