Andrey Godyaev
Andrey Godyaev

Reputation: 913

TypeScript: functional interface with constructor

I want a class that behaves like js Date function:

  1. when called via new it creates an instance of class.
  2. when called as a function it does some static stuff.

How do i implement this interface?

interface A {
    (): string;
    new(arg: number);
    GetValue(): number;
}

current solution that doesn't compile but produces correct js code in playground:

class B implements A {

    private Value: number;

    constructor(arg: number) {
        if (this.constructor == B) {
            this.Value = arg;
        } else {
            return "42";
        } 
    }

    GetValue(): number {
        return this.Value;
    }
} 

Upvotes: 0

Views: 298

Answers (1)

jcalz
jcalz

Reputation: 329533

You can't use an ES2015 or later class to let you call a constructor without the new keyword. In Step 2 of Section 9.2.1 of the linked documents, calling a class constructor without the new keyword should result in a TypeError being thrown. (If you target ES5 in TypeScript you'll get something that works at runtime, but if you target ES2015 or above you will get runtime errors. It's best not to do this.) So to implement your interface you will need to use a pre-ES2015 constructor function instead.


By the way, the new(arg: number) signature needs a return type. For example:

interface A {
  (): string;
  new(arg: number): AInstance; // return something
  GetValue(): number;
}
// define the instance type
interface AInstance { 
  instanceMethod(): void;
}

Here's one way to implement that. First, make a class for AInstance:

class _A implements AInstance {
  constructor(arg: number) { } // implement
  instanceMethod() { } // implement
}

Then, make a function that can be called with or without new:

const AFunctionLike =
  function(arg?: number): AInstance | string {
    if (typeof arg !== "undefined") {
      return new _A(arg);
    }
    return "string";
  } as { new(arg: number): AInstance, (): string };

I've decided that if you call AFunctionLike with an argument then you will get an AInstance, otherwise you will get a string. You can also check explicitly for whether new was used, via new.target, if your runtime has support for it.

Also note that I had to assert that AFunctionLike is newable (with the as clause on the last line) since there's currently no other way to tell TypeScript that a standalone function can be called with new.

Now we're almost done. We can declare a value of type A as follows:

const A: A = Object.assign(
  AFunctionLike,
  {
    GetValue() {
      return 1;
    }
  }
);

The value A has been formed by merging AFunctionLike with an object that implements GetValue(). You can use Object.assign or spread syntax to do this merging.


That's it. You can verify that this works at runtime on the TypeScript Playground. Good luck!

Upvotes: 2

Related Questions