How can I do constructor overloading in a derived class in TypeScript?

Lee H picture Lee H · Oct 2, 2014 · Viewed 10.9k times · Source

Assuming I have a 'base' class such as this:

class CcDefinition {
  // Some properties here

  constructor (json: string);
  constructor (someVar: number, someOtherVar: string);
  constructor (jsonOrSomeVar: any, someOtherVar?: string) {
    if (typeof jsonOrSomeVar=== "string") {
      // some JSON wrangling code here
    } else {
      // assign someVar and someOtherVar to the properties
    }
  }
}

I want to be able to extend this base class while still supporting constructor overloading. For example:

class CcDerived extends CcDefinition {
  // Some additional properties here

  constructor (json: string);
  constructor (someVar: boolean, someOtherVar: number, someAdditionalVar: string);
  constructor (jsonOrSomeVar: any, someOtherVar?: number, someAdditionalVar?: string) {
    if (typeof jsonOrSomeVar=== "string") {
      super.constructFromJson(jsonOrSomeVar);
    } else {
      super.constructFromDef(someOtherVar, someAdditionalVar);
      // assign someVar to the additional properties of this derived class
    }
  }
}

The problem is that Typescript demands that the 'super' keyword appear first (literally) in the constructor implementation. The specific build error message is:

"A 'super' call must be the first statement in the constructor when a class contains initialized properties or has parameter properties."

However, I need to determine which parameters I will pass into the 'super' (i.e. use a different constructor overload) based upon what was supplied to the extended (derived) class. You should assume here that the derived class' constructor overloads may be very different from the super's.

Is there a workaround for what I'm trying to achieve?

Answer

Ryan Cavanaugh picture Ryan Cavanaugh · Oct 2, 2014

This restriction only applies if you have initialized member properties in the derived class, so the first workaround is to simply only declare those properties and then initialize them in the derived class constructor.

In other words, you can change:

class CcDerived extends CcDefinition {
  y = 10;

  constructor (json: string);
  constructor (someVar: boolean, someOtherVar: number, someAdditionalVar: string);
  constructor (jsonOrSomeVar: any, someOtherVar?: number, someAdditionalVar?: string) {
    if (typeof jsonOrSomeVar=== "string") {
      super(jsonOrSomeVar);
    } else {
      super(someOtherVar, someAdditionalVar);
    }
  }
}

to this:

class CcDerived extends CcDefinition {
  // Some additional properties here
  y: number;

  constructor (json: string);
  constructor (someVar: boolean, someOtherVar: number, someAdditionalVar: string);
  constructor (jsonOrSomeVar: any, someOtherVar?: number, someAdditionalVar?: string) {
    this.y = 10;
    if (typeof jsonOrSomeVar=== "string") {
      super(jsonOrSomeVar);
    } else {
      super(someOtherVar, someAdditionalVar);
    }
  }
}

Note that the initialization order here is roughly the same as in other OOP languages and you need to be careful about not calling virtual methods from constructors, etc.

If that's too distasteful, note that the restriction is simply that the first statement be a super call. You can often refactor the super call:

class CcDerived extends CcDefinition {
  constructor (json: string);
  constructor (someVar: boolean, someOtherVar: number, someAdditionalVar: string);
  constructor (jsonOrSomeVar: any, someOtherVar?: number, someAdditionalVar?: string) {
      super(
          typeof jsonOrSomeVar === 'string' ? jsonOrSomeVar : someOtherVar,
          typeof jsonOrSomeVar === 'string' ? undefined : someAdditionalVar); 
  }
}

Not the prettiest, but it's at least semantically equivalent. This does assume your base class constructor is checking for undefined (instead of arguments.length) to determine which overload was invoked.