Constructor overload in TypeScript

TypescriptConstructorOverloading

Typescript Problem Overview


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.

Typescript Solutions


Solution 1 - Typescript

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

Solution 2 - Typescript

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.

Solution 3 - Typescript

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;
}());

Addendum: Setting default values: the wrong way

The || (or) operator

Consider 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);
Object.assign(this,params)

In my tests, using es6/typescript destructured object can be 15-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')

Solution 4 - Typescript

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.

Solution 5 - Typescript

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

Solution 6 - Typescript

I know this is an old question, but new in 1.4 is union types; use these for all function overloads (including constructors). Example:

class foo {
    private _name: any;
    constructor(name: string | number) {
        this._name = name;
    }
}
var f1 = new foo("bar");
var f2 = new foo(1);

Solution 7 - Typescript

Actually it might be too late for this answer but you can now do this:

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor();
    constructor(obj: IBox);
    constructor(obj?: IBox) {    
        this.x = !obj ? 0 : obj.x;
        this.y = !obj ? 0 : obj.y;
        this.height = !obj ? 0 : obj.height;
        this.width = !obj ? 0 : obj.width;
    }
}

so instead of static methods you can do the above. I hope it will help you!!!

Solution 8 - Typescript

You can handle this by :

class Box {
  x: number;
  y: number;
  height: number;
  width: number;
  constructor(obj?: Partial<Box>) {    
     assign(this, obj);
  }
}

Partial will make your fields (x,y, height, width) optionals, allowing multiple constructors

eg: you can do new Box({x,y}) without height, and width.

Solution 9 - Typescript

Your Box class is attempting to define multiple constructor implementations.

Only the last constructor overload signature is used as the class constructor implementation.

In the below example, note the constructor implementation is defined such that it does not contradict either of the preceding overload signatures.

interface IBox = {
    x: number;
    y: number;
    width: number;
    height: number;
}

class Box {
    public x: number;
    public y: number;
    public width: number;
    public height: number;

    constructor() /* Overload Signature */
    constructor(obj: IBox) /* Overload Signature */
    constructor(obj?: IBox) /* Implementation Constructor */ {
        if (obj) {
            this.x = obj.x;
            this.y = obj.y;
            this.width = obj.width;
            this.height = obj.height;
        } else {
            this.x = 0;
            this.y = 0;
            this.width = 0;
            this.height = 0
        }
    }

    get frame(): string {
        console.log(this.x, this.y, this.width, this.height);
    }
}

new Box().frame; // 0 0 0 0
new Box({ x:10, y:10, width: 70, height: 120 }).frame; // 10 10 70 120



// You could also write the Box class like so;
class Box {
    public x: number = 0;
    public y: number = 0;
    public width: number = 0;
    public height: number = 0;

    constructor() /* Overload Signature */
    constructor(obj: IBox) /* Overload Signature */
    constructor(obj?: IBox) /* Implementation Constructor */ {
        if (obj) {
            this.x = obj.x;
            this.y = obj.y;
            this.width = obj.width;
            this.height = obj.height;
        }
    }

    get frame(): string { ... }
}

Solution 10 - Typescript

In the case where an optional, typed parameter is good enough, consider the following code which accomplishes the same without repeating the properties or defining an interface:

export class Track {
   public title: string;
   public artist: string;
   public lyrics: string;

   constructor(track?: Track) {
     Object.assign(this, track);
   }
}

Keep in mind this will assign all properties passed in track, eve if they're not defined on Track.

Solution 11 - Typescript

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) {
        const { x, y, height, width } = { x: 0, y: 0, height: 0, width: 0, ...obj }
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
}

Solution 12 - 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" })

Solution 13 - Typescript

I use the following alternative to get default/optional params and "kind-of-overloaded" constructors with variable number of params:

private x?: number;
private y?: number;

constructor({x = 10, y}: {x?: number, y?: number}) {
 this.x = x;
 this.y = y;
}

I know it's not the prettiest code ever, but one gets used to it. No need for the additional Interface and it allows private members, which is not possible when using the Interface.

Solution 14 - Typescript

Here is a working example and you have to consider that every constructor with more fields should mark the extra fields as optional.

class LocalError {
  message?: string;
  status?: string;
  details?: Map<string, string>;

  constructor(message: string);
  constructor(message?: string, status?: string);
  constructor(message?: string, status?: string, details?: Map<string, string>) {
    this.message = message;
    this.status = status;
    this.details = details;
  }
}

Solution 15 - Typescript

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.

Solution 16 - Typescript

Generally speaking for N overloads, it might be better to use:

constructor(obj?: {fromType1: IType1} | {fromType2: IType2}) {    
    if(obj){
      if(obj.fromType1){
        //must be of form IType1
      } else if(obj.fromType2){
        //must have used a IType2
      } else {
        throw "Invalid argument 1"
      }
    } else {
      //obj not given
    }
}   

At least now we can check which route to go down and act accordingly

Solution 17 - Typescript

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")

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTedView Question on Stackoverflow
Solution 1 - TypescriptchuckjView Answer on Stackoverflow
Solution 2 - TypescriptkbtzView Answer on Stackoverflow
Solution 3 - TypescriptBensonView Answer on Stackoverflow
Solution 4 - TypescriptShinNoNoirView Answer on Stackoverflow
Solution 5 - Typescriptvegemite4meView Answer on Stackoverflow
Solution 6 - TypescriptJoeView Answer on Stackoverflow
Solution 7 - TypescriptKostas DrakView Answer on Stackoverflow
Solution 8 - TypescriptYacineView Answer on Stackoverflow
Solution 9 - TypescriptMudlabsView Answer on Stackoverflow
Solution 10 - TypescriptparliamentView Answer on Stackoverflow
Solution 11 - TypescriptArdeshir ValipoorView Answer on Stackoverflow
Solution 12 - Typescriptmiguel savignanoView Answer on Stackoverflow
Solution 13 - Typescriptynotu.View Answer on Stackoverflow
Solution 14 - TypescriptRadu LinuView Answer on Stackoverflow
Solution 15 - TypescriptrustyBucketBayView Answer on Stackoverflow
Solution 16 - TypescriptSancarnView Answer on Stackoverflow
Solution 17 - TypescriptAdrianoView Answer on Stackoverflow