3

Function Register is instantiated by calling Register('something'). Typescript says this is only possible if new is used on a void returning function . In this case Register is returning an instance of itself. How should I type this in Typescript?

module Register {

   export function Register(x: string): Instance {
      if (!(this instanceof Register)) {
          return new Register(x)
      }
      this.value = x
   }
   ...

   export interface Instance {
      new(x: string): Instance;
      ...
   }

}
export = Register
Faris Zacina
  • 14,056
  • 7
  • 62
  • 75
donnut
  • 700
  • 9
  • 19
  • 1
    The answer probably is: "You should not do this in TypeScript", but you should use classes and static methods instead. – Niko Oct 10 '14 at 09:20
  • I hope not. Seems like a legitimate construction to me. – donnut Oct 10 '14 at 09:32
  • Similar questions: [How does typescript interfaces with construct signatures work?](http://stackoverflow.com/questions/13407036/how-does-typescript-interfaces-with-construct-signatures-work) and [IOC for TypeScript](http://stackoverflow.com/questions/12795666/ioc-for-typescript) – xmojmr Oct 10 '14 at 12:16
  • @xmojmr As far as I understand the answers to these questions describe constructors that take another constructor as their argument. This seems different from my question that concerns a function with a (string) argument that makes sure it instantiates itself if necessary, when called without `new`. – donnut Oct 10 '14 at 12:39
  • The linked questions show how to implement [class factory pattern](http://addyosmani.com/resources/essentialjsdesignpatterns/book/#factorypatternjavascript) where your `Register` function would be the [factory method](http://en.wikipedia.org/wiki/Factory_method_pattern). If you are looking only for a TypeScript syntax hack needed to make a legacy code base compile then I've misunderstood your intents and the linked questions are not relevant, sorry – xmojmr Oct 10 '14 at 13:16
  • @xmojmr Thanks! The factory pattern still requires you to do something like `Register.create()`. The solution I am looking for is how to self-instantiate an object. This is a pattern as well and I do not agree it is legacy code. – donnut Oct 10 '14 at 13:38

2 Answers2

1

Maybe your example is simplified, and you are trying to achieve something more complex under the hood, but If i understand your code correctly you just want to return an instance of the Register function without the New operator.

The only option i can think of is to trick the TS compiler, and to specify the return type as void, and then in your variables use type any.

module Register {

    export function Register(x: string): void {
        if (!(this instanceof Register)) {
            return new Register(x);
        }
        this.value = x;
    }

    export interface Instance {
        new (x: string): Instance;
        value: string;
    }

}

export = Register;

var rega: any = Register.Register("something");
console.log(rega.value); // something

Update: Since you have a problem with specifying any as the explicit type for each variable, then you could use Object.create() instead of the new operator:

module Register {

    export function Register(x: string): Instance {
        var r = Object.create(Register);
        r.value = x;
        return r;
    }

    export interface Instance {
        new (x: string): Instance;
        value: string;
    }

}

export = Register;

var rega = Register.Register("something");

console.log(rega.value); // something
Faris Zacina
  • 14,056
  • 7
  • 62
  • 75
  • The example is simplified indeed. What this construct does is avoid having to use the `new` keyword to instantiate, so `Register.Register('something')` would be the same as `new Register.Register('something')` – donnut Oct 10 '14 at 10:36
  • Ok cool. I have updated my answer with a hack that could work :) – Faris Zacina Oct 10 '14 at 10:46
  • Thanks, this is how I used it up to now, applying type `any`. It works, but I hope Typescript can do better. Everywhere you pass Register you need to specify the type `any` instead of `Register.Instance`, which is not what you would like. – donnut Oct 10 '14 at 10:55
  • @ZenCoder I did and this works! And, to add methods to its prototype you now need to write: `Register.map = function() {}` – donnut Oct 10 '14 at 14:12
0

I suspect you're having the same problem that I ran into (that brought me here).

I was trying to add some TypeScript type definitions (.d.ts) for an existing pure JavaScript (.js) project.

The JavaScript source was a traditional constructor function (with prototype class methods) that detected if it was called without new and did the right thing:

const MyObject = function (options) {
    if (!(this instanceof MyObject)) {
        return new MyObject(options);
    }

    // ...
}

MyObject.prototype.myMethod = function() {
    // Do something...
}

module.exports = MyObject;

My understanding of the "correct" way to add TypeScript types for this kind of JavaScript object is with an interface:

declare interface MyObject {
  /**
   * Constructor interface
   */
  new (options?: Options): this;

  /**
   * Functional interface
   */
  (options?: Options): this;

  /**
   * JSDoc ftw!
   */
  myMethod(): void;
}

// Needed so we can export a variable, not just a type
declare const MyObject: MyObject;

// Since the JavaScript is not exporting a "module" (object with a `default` parameter):
export = MyObject;
Cameron Tacklind
  • 5,764
  • 1
  • 36
  • 45