Constructor Overload in Typescript
Has Anybody Done Constructor Overloading in Typescript. on Page 64 of the Language Specification (V 0.8), There Are Statements Describing Constructor...
Has anybody done constructor overloading in TypeScript. On page 64 of the language specification (v 0.8), there are statements describing constructor overloads, but there wasn't any sample code given.
I'm trying out a really basic class declaration right now; it looks like this,
interface IBox {
x : number;
y : number;
height : number;
width : number;
}
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor(obj: IBox) {
this.x = obj.x;
this.y = obj.y;
this.height = obj.height;
this.width = obj.width;
}
constructor() {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
}
When ran with tsc BoxSample.ts, it throws out a duplicate constructor definition -- which is obvious. Any help is appreciated.
17 Answers
TypeScript allows you to declare overloads but you can only have one implementation and that implementation must have a signature that is compatible with all overloads. In your example, this can easily be done with an optional parameter as in,
interface IBox {
x : number;
y : number;
height : number;
width : number;
}
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor(obj?: IBox) {
this.x = obj?.x ?? 0
this.y = obj?.y ?? 0
this.height = obj?.height ?? 0
this.width = obj?.width ?? 0;
}
}
or two overloads with a more general constructor as in,
interface IBox {
x : number;
y : number;
height : number;
width : number;
}
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {
this.x = obj?.x ?? 0
this.y = obj?.y ?? 0
this.height = obj?.height ?? 0
this.width = obj?.width ?? 0;
}
}
See in Playground
Regarding constructor overloads one good alternative would be to implement the additional overloads as static factory methods. I think its more readable and easier than checking for all possible argument combinations at the constructor.
In the following example we're able to create a patient object using data from an insurance provider which stores values differently. To support yet another data structure for patient instantiation, one could simply add another static method to call the default constructor the best it can after normalizing the data provided.
class Patient {
static fromInsurance({
first, middle = '', last,
birthday, gender
}: InsuranceCustomer): Patient {
return new this(
`${last}, ${first} ${middle}`.trim(),
utils.age(birthday),
gender
);
}
constructor(
public name: string,
public age: number,
public gender?: string
) {}
}
interface InsuranceCustomer {
first: string,
middle?: string,
last: string,
birthday: string,
gender: 'M' | 'F'
}
const utils = { /* included in the playground link below */};
{// Two ways of creating a Patient instance
const
jane = new Patient('Doe, Jane', 21),
alsoJane = Patient.fromInsurance({
first: 'Jane', last: 'Doe',
birthday: 'Jan 1, 2000', gender: 'F'
})
console.clear()
console.log(jane)
console.log(alsoJane)
}
You can check the output at TS Playground
Method overloading in TypeScript isn't for real, let's say, as it would require too much compiler-generated code and TS is designed to avoid that at all costs. The main use case for method overloading is probably writing declarations for libraries that have magic arguments in their API. Since all the heavy-lifting of handling different sets of possible arguments is done by you I don't see much advantage in using overloads rather than ad-hoc methods for each scenario.
It sounds like you want the object parameter to be optional, and also each of the properties in the object to be optional. In the example, as provided, overload syntax isn't needed. I wanted to point out some bad practices in some of the answers here. Granted, it's not the smallest possible expression of essentially writing box = { x: 0, y: 87, width: 4, height: 0 }, but this provides all the code hinting niceties you could possibly want from the class as described. This example allows you to call a function with one, some, all, or none of the parameters and still get default values.
/** @class */
class Box {
public x?: number;
public y?: number;
public height?: number;
public width?: number;
constructor(params: Box = {} as Box) {
// Define the properties of the incoming `params` object here.
// Setting a default value with the `= 0` syntax is optional for each parameter
let {
x = 0,
y = 0,
height = 1,
width = 1
} = params;
// If needed, make the parameters publicly accessible
// on the class ex.: 'this.var = var'.
/** Use jsdoc comments here for inline ide auto-documentation */
this.x = x;
this.y = y;
this.height = height;
this.width = width;
}
}
Need to add methods? A verbose but more extendable alternative:
The Box class above can work double-duty as the interface since they are identical. If you choose to modify the above class, you will need to define and reference a new interface for the incoming parameters object since the Box class no longer would look exactly like the incoming parameters. Notice where the question marks (?:) denoting optional properties move in this case. Since we're setting default values within the class, they are guaranteed to be present, yet they are optional within the incoming parameters object:
interface BoxParams {
x?: number;
// Add Parameters ...
}
class Box {
public x: number;
// Copy Parameters ...
constructor(params: BoxParams = {} as BoxParams) {
let { x = 0 } = params;
this.x = x;
}
doSomething = () => {
return this.x + this.x;
}
}
Whichever way you choose to define your class, this technique offers the guardrails of type safety, yet the flexibility write any of these:
const box1 = new Box();
const box2 = new Box({});
const box3 = new Box({x:0});
const box4 = new Box({x:0, height:10});
const box5 = new Box({x:0, y:87,width:4,height:0});
// Correctly reports error in TypeScript, and in js, box6.z is undefined
const box6 = new Box({z:0});
Compiled, you see how the default settings are only used if an optional value is undefined; it avoids the pitfalls of a widely used (but error-prone) fallback syntax of var = isOptional || default; by checking against void 0, which is shorthand for undefined:
The Compiled Output
var Box = (function () {
function Box(params) {
if (params === void 0) { params = {}; }
var _a = params.x, x = _a === void 0 ? 0 : _a, _b = params.y, y = _b === void 0 ? 0 : _b, _c = params.height, height = _c === void 0 ? 1 : _c, _d = params.width, width = _d === void 0 ? 1 : _d;
this.x = x;
this.y = y;
this.height = height;
this.width = width;
}
return Box;
}());