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 wa
Update 2 (28 September 2020): This language is constantly evolving, and so if you can use Partial
(introduced in v2.1) then this is now my preferred way to achieve this.
class Box {
x: number;
y: number;
height: number;
width: number;
public constructor(b: Partial<Box> = {}) {
Object.assign(this, b);
}
}
// Example use
const a = new Box();
const b = new Box({x: 10, height: 99});
const c = new Box({foo: 10}); // Will fail to compile
Update (8 June 2017): guyarad and snolflake make valid points in their comments below to my answer. I would recommend readers look at the answers by Benson, Joe and snolflake who have better answers than mine.*
Original Answer (27 January 2014)
Another example of how to achieve constructor overloading:
class DateHour {
private date: Date;
private relativeHour: number;
constructor(year: number, month: number, day: number, relativeHour: number);
constructor(date: Date, relativeHour: number);
constructor(dateOrYear: any, monthOrRelativeHour: number, day?: number, relativeHour?: number) {
if (typeof dateOrYear === "number") {
this.date = new Date(dateOrYear, monthOrRelativeHour, day);
this.relativeHour = relativeHour;
} else {
var date = <Date> dateOrYear;
this.date = new Date(date.getFullYear(), date.getMonth(), date.getDate());
this.relativeHour = monthOrRelativeHour;
}
}
}
Source: http://mimosite.com/blog/post/2013/04/08/Overloading-in-TypeScript
We can simulate constructor overload using guards
interface IUser {
name: string;
lastName: string;
}
interface IUserRaw {
UserName: string;
UserLastName: string;
}
function isUserRaw(user): user is IUserRaw {
return !!(user.UserName && user.UserLastName);
}
class User {
name: string;
lastName: string;
constructor(data: IUser | IUserRaw) {
if (isUserRaw(data)) {
this.name = data.UserName;
this.lastName = data.UserLastName;
} else {
this.name = data.name;
this.lastName = data.lastName;
}
}
}
const user = new User({ name: "Jhon", lastName: "Doe" })
const user2 = new User({ UserName: "Jhon", UserLastName: "Doe" })
You should had in mind that...
contructor()
constructor(a:any, b:any, c:any)
It's the same as new()
or new("a","b","c")
Thus
constructor(a?:any, b?:any, c?:any)
is the same above and is more flexible...
new()
or new("a")
or new("a","b")
or new("a","b","c")
As commented in @Benson answer, I used this example in my code and I found it very useful. However I found with the Object is possibly 'undefined'.ts(2532)
error when I tried to make calculations with my class variable types, as the question mark leads them to be of type AssignedType | undefined
. Even if undefined case is handled in later execution or with the compiler type enforce <AssignedType>
I could not get rid of the error, so could not make the args optional.I solved creating a separated type for the arguments with the question mark params and the class variables without the question marks. Verbose, but worked.
Here is the original code, giving the error in the class method(), see below:
/** @class */
class Box {
public x?: number;
public y?: number;
public height?: number;
public width?: number;
// The Box class can work double-duty as the interface here since they are identical
// If you choose to add methods or modify this class, you will need to
// define and reference a new interface for the incoming parameters object
// e.g.: `constructor(params: BoxObjI = {} as BoxObjI)`
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
const {
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;
}
method(): void {
const total = this.x + 1; // ERROR. Object is possibly 'undefined'.ts(2532)
}
}
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 });
So variable cannot be used in the class methods. If that is corrected like this for example:
method(): void {
const total = <number> this.x + 1;
}
Now this error appears:
Argument of type '{ x: number; y: number; width: number; height: number; }' is not
assignable to parameter of type 'Box'.
Property 'method' is missing in type '{ x: number; y: number; width: number; height:
number; }' but required in type 'Box'.ts(2345)
As if the whole arguments bundle was no optional anymore.
So if a type with optional args is created, and the class variables are removed from optional I achieve what I want, the arguments to be optional, and to be able to use them in the class methods. Below the solution code:
type BoxParams = {
x?: number;
y?: number;
height?: number;
width?: number;
}
/** @class */
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
// The Box class can work double-duty as the interface here since they are identical
// If you choose to add methods or modify this class, you will need to
// define and reference a new interface for the incoming parameters object
// e.g.: `constructor(params: BoxObjI = {} as BoxObjI)`
constructor(params: BoxParams = {} as BoxParams) {
// Define the properties of the incoming `params` object here.
// Setting a default value with the `= 0` syntax is optional for each parameter
const {
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;
}
method(): void {
const total = this.x + 1;
}
}
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 });
Comments appreciated from anyone who takes the time to read and try to understand the point I am trying to make.
Thanks in advance.
Note: this was simplified and updated 4/13/2017 to reflect TypeScript 2.1, see history for TypeScript 1.8 answer.
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;
// The Box class can work double-duty as the interface here since they are identical
// If you choose to add methods or modify this class, you will need to
// define and reference a new interface for the incoming parameters object
// e.g.: `constructor(params: BoxObjI = {} as BoxObjI)`
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;
}
}
This is a very safe way to write for parameters that may not have all properties of the object defined. You can now safely 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 that the optional parameters truly are optional, that 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
:
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;
}());
||
(or) operatorConsider the danger of ||
/or operators when setting default fallback values as shown in some other answers. This code below illustrates the wrong way to set defaults. You can get unexpected results when evaluating against falsey values like 0, '', null, undefined, false, NaN:
var myDesiredValue = 0;
var result = myDesiredValue || 2;
// This test will correctly report a problem with this setup.
console.assert(myDesiredValue === result && result === 0, 'Result should equal myDesiredValue. ' + myDesiredValue + ' does not equal ' + result);
In my tests, using es6/typescript destructured object can be almost 90% faster than Object.assign. Using a destructured parameter only allows methods and properties you've assigned to the object. For example, consider this method:
class BoxTest {
public x?: number = 1;
constructor(params: BoxTest = {} as BoxTest) {
Object.assign(this, params);
}
}
If another user wasn't using TypeScript and attempted to place a parameter that didn't belong, say, they might try putting a z
property
var box = new BoxTest({x: 0, y: 87, width: 4, height: 0, z: 7});
// This test will correctly report an error with this setup. `z` was defined even though `z` is not an allowed property of params.
console.assert(typeof box.z === 'undefined')
Note that you can also work around the lack of overloading at the implementation level through default parameters in TypeScript, e.g.:
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 = {x:0,y:0, height:0, width:0}) {
this.x = obj.x;
this.y = obj.y;
this.height = obj.height;
this.width = obj.width;
}
}
Edit: As of Dec 5 '16, see Benson's answer for a more elaborate solution that allows more flexibility.