591

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.

Kamil Naja
  • 6,267
  • 6
  • 33
  • 47
Ted
  • 6,011
  • 2
  • 13
  • 4

18 Answers18

431

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

Flavio Vilante
  • 5,131
  • 1
  • 11
  • 15
chuckj
  • 27,773
  • 7
  • 53
  • 49
  • 20
    Actually, it should be possible to let the compiler generate javascript to determine at run-time which overload was taken. But this is unlikely because their philosophy seems te be to generate as little javascript as possible. – remcoder Feb 01 '14 at 20:58
  • @remcoder, that would always be true. Some kinds of type information are not available at runtime. For example, there is no concept of the interface `IBox` in the generated JavaScript. It could work for classes and built in types, but I suppose given the potential confusion around this it was omitted. – Drew Noakes Feb 27 '14 at 11:27
  • 3
    Another very important note: while TypeScript is already not typesafe, this further invades it. Function overloading like done here loses any properties that can be checked about the function. The compiler won't care anymore and will assume the returned types to be correct. – froginvasion Aug 06 '14 at 08:19
  • 3
    What makes this not type safe? We're still ensuring that the type is ```number``` with ```public x: number```. The safety comes in that we're making sure that parameters, if passed, are of a correct type. – nikk wong Apr 19 '16 at 05:53
  • @nikkwong froginvasion's point was that using this technique TypeScript doesn't verify the correctness of the overloaded implementation with respect to the overloads. The call sites are verified but the implementation is not. Though not "typesafe", using froginvasion's implied definition, it does limit the code that can be blamed for type errors to the overloaded implementation. – chuckj Apr 20 '16 at 23:06
  • Any specific reason for using `cond && ifTrue || ifFalse` instead of `cond ? ifTrue : ifFalse`? – Nic Sep 27 '18 at 23:52
  • They don't mean the same thing. For example, `obj && obj.x || 0` will be `0` if `obj.x` is `undefined`, which is what I intended, where `obj ? obj.x : 0` will be `undefined` if `obj.x` is `undefined` which is not what I wanted. – chuckj Nov 20 '18 at 00:16
221

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.

cvsguimaraes
  • 12,910
  • 9
  • 49
  • 73
  • 9
    you could use `(data: Partial)` if you don't always want to require `first`, `last`, and `birthday` to be present in `data`. – Cabrera Mar 16 '19 at 17:10
  • 4
    Also, the access modifier of the constructor can be changed from `public` to `private`/`protected`, and then the only way to create an object is static factory methods. Sometimes this can be very useful. – Oleg Zarevennyi Dec 07 '20 at 10:10
  • One of the main differences is that child static methods must be subtypes of the parent static method, whereas there's no restriction on child constructors at all. – CMCDragonkai May 07 '22 at 04:04
  • Great answer! tx. Particularly useful in any situation where the constructor's arguments don't match those of the additional method you want to make. – Steven Schkolne May 17 '22 at 22:55
  • +10 for perseverance – JΛYDΞV Oct 27 '22 at 20:41
106

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

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')
Benson
  • 4,181
  • 2
  • 26
  • 44
  • I know this is kind of an old thread but the Ibox casting broke my mind, can you explain it to me how it works? – Nickso Apr 11 '17 at 18:42
  • 2
    I've updated my answer to remove the superfluous casting that was a carryover from coding for Typescript 1.8. The casting that remains is for the empty object ( {} becomes the default object if no parameters are defined; and since `{}` doesn't validate as a Box, we cast it as a Box. Casting it this way allow us to create a new Box with none of its parameters defined. In your IDE, you can input my example, as well as the `const box1 = new Box();` lines, and you can see how casting solves some of the error messages we see in the usage scenarios. – Benson Apr 13 '17 at 08:02
  • @Benson the BoxTest example contains errors. The TypeScript compiler correctly complains about the wrong usage of the constructor, but the assignment will still occur. The assertion fails because `box.z` _is_ in fact `7` in your code, not `undefined`. – Steven Liekens Apr 13 '17 at 09:58
  • _when evaluating against falsey values like 0, '', {}_ - `{}` is **not** a falsy value. – Marius Apr 16 '17 at 21:44
  • @Steven The BoxTest is an example of the wrong way to assign object (if your intent is to be strict on the allowed members of your object). It is designed to fail. This test proves Object assign incorrectly permits us to define a box.z, whereas using a destructured object, it is not possible to set z using the constructor. Run the same assertion using the original Box class, and we see that the test does not fail. – Benson Apr 16 '17 at 22:32
  • @Marius Thanks, facepalm! :) I've corrected the falsey value line. – Benson Apr 16 '17 at 22:37
  • @Benson sorry I think I misread something earlier. I don't know what I was thinking. – Steven Liekens Apr 17 '17 at 09:22
  • 1
    Added a method to the Box class and then the constructor stop working (failed at compile time). Any idea? – JeeShen Lee Dec 13 '17 at 07:45
  • 1
    @JeeShenLee you could either extend the Box class to a newly-named class with methods or create an interface for the parameters expected. The interface type is borrowed from the Box class since classes can act as interfaces. With your added method, the interface was expecting a method to be passed in as part of the object since the class is doing double-duty as the interface. Just copy the first five lines of the Box class and change it to an interface with a new name, such as `interface BoxConfig { x?: number ...}` and then change the line `constructor(obj: BoxConfig = {} as BoxConfig) {` – Benson Jan 06 '18 at 12:34
  • @Benson, bit of a digression from the original question, but is it possible to adapt this approach to TypeScript classes that also have methods defined? I'm in a situation where the compiler complains because the object I pass into the class constructor doesn't contain methods on the original class. – miqh Dec 21 '18 at 01:21
  • @miqh Yes, you can modify the class to add methods: see my answer to JeeShenLee above. You just need to define your own separate interface for the incoming object. – Benson Dec 28 '18 at 19:41
  • Copying the class as an interface sounds very wrong. I have just tested your proposed solution with an extended class and it doesn't really work. If you extend the Box class and add a method to, say someMethod() to ExtendedBox class, then the compiler will give you an error that someMethod() is missing in Box, but is required in ExtendedBox when you try to create a new ExtendedBox object. Can you elaborate as your comment to JeeShenLee doesn't really make sense. Thank you. – Paul Strupeikis Aug 10 '19 at 10:54
  • @PaulStrupeikis Please read my `// comment` above the constructor in the Box class. It says if you need to modify the class, you will need to define a unique interface (one that isn't also the Box class) for the incoming parameters object. In practice, you're probably correct, but for the answer here, I felt the extra interface to be unnecessary clutter given the goals of the class. – Benson Aug 15 '19 at 10:18
  • @Benson I followed this method (but defined a new interface as suggested) but when I tried to instantiate the class with only 2 of the 3 parameters defined in my constructor object I get a compiler error telling me that the object is not assignable to my interface type because it is missing the third parameter? – mark_h Jan 08 '20 at 12:11
  • @mark_h is your interface setup correctly? Are you adding a question mark to the interface items that are optional to denote them as optional? Ex: `interface MyBox { x?: number; }` – Benson Jan 09 '20 at 01:45
  • @Benson no my interface was not set up correctly. I found a solution through use of Partial but I will try using x? as you suggest. Thanks. – mark_h Jan 09 '20 at 08:02
  • 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 `` 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. – rustyBucketBay Oct 20 '20 at 19:16
  • Actually I am posting this in an answer below with a snippet code below hoping for some interesting comments. – rustyBucketBay Oct 20 '20 at 19:25
82

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.

Community
  • 1
  • 1
ShinNoNoir
  • 2,274
  • 1
  • 18
  • 24
52

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

vegemite4me
  • 6,621
  • 5
  • 53
  • 79
  • 24
    This isn't a constructive comment - but, wow, this is ugly. Kind of misses the point of *type* safety in TypeScript... – Guy Mar 01 '15 at 10:04
  • 2
    Is that a constructor overload?! No thanks! I'd rather implement a static factory method for that class, really ugly indeed. – cvsguimaraes Jul 31 '16 at 20:32
  • 2
    I suppose today we could have dateOrYear: Date | number, – Pascal Ganaye Apr 01 '17 at 17:18
  • I think it's a very good way to have something like the object initializer in c#. Ok, you open the door having a problem in some situations but it's not all objects that should have guards to prevent bad initialization. POCO or DTO as example are good candidates for this implementation. I would also put the parameter nullable to allow an empty constructor like this: args?: Partial – Samuel Jun 23 '21 at 10:36
34

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);
Joe
  • 1,295
  • 13
  • 16
  • Wouldn't the `name` field also be of type `string | number` instead of `any`? – Carcigenicate Apr 04 '16 at 15:47
  • You could definitely do that, yes, and it might be a bit more consistent, but in this example, it'd only give you access to `.toString()` and `.valueOf()`, in Intellisense, so for me, using `any` is just fine, but to each his/her own. – Joe Apr 05 '16 at 13:20
17

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!!!

Kostas Drak
  • 3,222
  • 6
  • 28
  • 60
  • Great! You have to consider here, that every new extra field of other constructors should be marked as optional; like you already did for `obj?` – Radu Linu Mar 04 '20 at 08:37
  • 1
    Isn't the second constructor ```constructor(obj: IBox);``` redundant? Isn't the last one taking care of these both cases? – zaplec Apr 15 '20 at 11:45
9

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.

Yacine MEDDAH
  • 1,211
  • 13
  • 17
  • 2
    I think you still need to handle default values for missing items. Easily done, tho. – Charlie Reitzel Oct 09 '19 at 01:09
  • 1
    or `constructor(obj?: Partial)` +1 – naveen May 17 '20 at 12:12
  • 1
    Partials are a great answer, but why introduce lowdash? – vegemite4me Sep 28 '20 at 10:11
  • @vegemite4me you're right no need for lodash. Object.assign is sufficient – Yacine MEDDAH Nov 05 '20 at 09:06
  • 1
    Careful, this solution breaks the class contract as `Box` defines that all properties are mandatory, while this solution allows them to be undefined. – DarkNeuron Nov 26 '20 at 14:38
  • @DarkNeuron yes all the fields are mandatory, but there is no validation at this level. You can definitely use a less permissive constructor, it'll never block the developer to pass a falsy value such as `undefined` or `null` ¯\_(ツ)_/¯ I've been using it on a big projet more than 2 years. Never had a problem with it. – Yacine MEDDAH Nov 26 '20 at 15:35
7

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 { ... }
}
Mudlabs
  • 551
  • 5
  • 16
4

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.

parliament
  • 21,544
  • 38
  • 148
  • 238
4
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;
    }
}
2

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" })
miguel savignano
  • 1,109
  • 13
  • 9
1

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.

yN.
  • 1,847
  • 5
  • 30
  • 47
1

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;
  }
}
Radu Linu
  • 1,143
  • 13
  • 29
1

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.

rustyBucketBay
  • 4,320
  • 3
  • 17
  • 47
  • Yes, this is exactly how to use my method when doing customizations (the comment above the constructor directs to the exact solution you have here). A few people have been tripped up on it--my stealing the interface from the class--so I'm tempted to modify my answer. But I'll leave it for history as having my answer "as is" is a required point of reference in your great answer here. – Benson Jan 17 '21 at 22:22
  • OK I see. Thanks for clarifyng – rustyBucketBay Jan 18 '21 at 08:25
1

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

Sancarn
  • 2,575
  • 20
  • 45
0

As chuckj said, the simple answer is an optional parameter, but what if we want to overload a constructor with more than one parameter, or we want to change parameter order?

Turns out, constructors can be overloaded just like functions:

class FooBar {
  public foo?: number;
  public bar?: string;

  // Constructor A
  constructor(foo: number, bar?: string);
  // Constructor B
  constructor(bar: string, foo?: number);
  // Constructor C
  constructor(bar: string);
  // Constructor D
  constructor(foo: number);
  // Constructor E
  constructor();

  constructor(...args: any[]) {
    switch (args.length) {
      case 2:
        if (typeof args[0] === "number") {
          this.foo = args[0];
          this.bar = args[1];
        } else {
          this.bar = args[0];
          this.foo = args[1];
        }
        break;
      case 1:
        if (typeof args[0] === "number") {
          this.foo = args[0];
        } else {
          this.bar = args[0];
        }
    }

    console.log(this.foo, this.bar);
  }
}

const fooBarConstructorA = new FooBar("150", 25);
const fooBarConstructorB = new FooBar(25, "150");
const fooBarConstructorC = new FooBar("150");
const fooBarConstructorD = new FooBar("150");
const fooBarConstructorE = new FooBar();
Alexander
  • 51
  • 1
  • 5
-6

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

Ivan
  • 34,531
  • 8
  • 55
  • 100
Adriano
  • 1
  • 1