I received the following interface from a library that I'm using:
export interface LatLng { constructor(lat: number, lng: number): void; lat(): number; lng(): number; }
How do I create an implementation of this class? (I need a testing mock) A natural-looking implementation with constructor defined as:
export class LatLngImpl implements LatLng { constructor(private _lat: number, private _lng: number) { }
doesn't compile:
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 read about contructor-interfaces in typescript, but I don't think it's applicable here.
Edit:
What I don't undertand is this constructor()
declaration in the interface. The interfaces with constructor signatures use new ()
syntax.
TypeScript defines a constructor using the constructor keyword. A constructor is a function and hence can be parameterized. The this keyword refers to the current instance of the class. Here, the parameter name and the name of the class's field are the same.
Classes in TypeScript do not require you to explicitly write a constructor. However if you are extending a base class you will need to create a constructor to call super() at a minimum.
No, you cannot have a constructor within an interface in Java. You can have only public, static, final variables and, public, abstract, methods as of Java7. From Java8 onwards interfaces allow default methods and static methods.
A TypeScript Interface can include method declarations using arrow functions or normal functions, it can also include properties and return types. The methods can have parameters or remain parameterless.
To put it in simple terms, “a constructor is a function which is called to create an object of the class, in which it is defined. It prepares a new object, for which often accepting arguments is used to set the required variables of the class”. But in TypeScript, unlike any other object-oriented language, only one constructor is allowed.
Let’s learn more about constructors and how we’ll use constructors in interfaces: Constructors are also a code feature heavily used in TypeScript codebases too. The TypeScript docs have a great example of constructor usage: Constructors essentially allow the creation of objects from classes.
And both of them will be transpiled from TypeScript (design time and compile time) to JavaScript with same result, but writing significantly less code: Constructors of derived classes have to call the base class constructor with super ().
One of the most common uses of interfaces in languages like C# and Java, that of explicitly enforcing that a class meets a particular contract, is also possible in TypeScript. ts interfaceClockInterface{ currentTime: Date;
Where is this library from? Whoever wrote it deserves a stern talking-to. Either it's a mistake (in which case they never tested it) or it's intentional but eeeeevil: using the not-quite-reserved word constructor
as an identifier for an instance method is just asking for trouble.
EDIT 2019-04-25: The trouble is worse than just "this is a bad idea"; it actually looks like once JavaScript natively supports class fields, it is going to be an error to have a class with a property named "constructor"
. TypeScript will change to reflect this in version 3.5+, so the implementation below will stop working then. See recently opened GitHub issue Microsoft/TypeScript#31020 for more information. After TS3.5 it looks like there will be no way to have a class with an instance constructor
property that is not the actual constructor function itself.
Either the library author intended to refer to a real constructor that you call with new
, in which case they should do something like @Series0ne's suggestion; or the author really wants to use an instance method to initialize the object, in which case they should do everyone a favor and use a more conventional method name like init()
.
In either case nobody should accept the interface you've been given, and certainly nobody should implement it.
Let's implement it:
export class LatLngImpl implements LatLng { private _lat: number; private _lng: number; lat() { return this._lat; } lng() { return this._lng; } // here's the important part, but see Microsoft/TypeScript#31020 "constructor"(lat: number, lng: number) { this._lat = lat; this._lng = lng; } }
The trick is to use the string literal "constructor"
instead of the bare word constructor
. From the TypeScript spec:
String literals may be used to give properties names that are not valid identifiers
By using the string literal, we were able to declare it as an instance method, and not the static class constructor method that is invoked when you call new
, and it compiles happily.
Now we can use it, if we dare:
const latLng = new LatLngImpl(); latLng.constructor(47.6391132, -122.1284311); // 😠 Why not init()? console.log("Latitude: " + latLng.lat() + ", Longitude: " + latLng.lng());
Yuck, we shouldn't have dared.
EDIT AGAIN 2019-04-25 The above quoted-string implementation will not work starting in TypeScript 3.5, and the correct answer to this will be "you can't do this" and "use a real constructor instead".
Hope that helps; good luck!
A constructor is technically a special, static function call that returns an instance of itself, so it doesn't really make sense for it to be part of an interface, because interface members are instance bound.
Typescript has a bit of a compiler trick to make them statically bound, and uses this for ambient declarations.
In your case, to provide an implementation, you need to remove the constructor from the interface:
interface LatLng { lat(): number; lng(): number; } class LatLngImpl implements LatLng { constructor(lat: number, lng: number) { } lat(): number { return 0; } lng(): number { return 0; } }
If LatLng
is implemented elsewhere, you simply need to provide an ambient declaration:
interface LatLng { lat(): number; lng(): number; } interface LatLngConstructor { new(lat: number, lng: number): LatLng; } declare var LatLng: LatLngConstructor;
Notice that LatLngConstructor
defines a new(...): LatLng
, which is what describes the constructor signature.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With