Document that property of interface is a class and not instance of class

I have some legacy AngularJs code which I am trying to document for a new project of ours using Typescript, but I have run into a bit of an odd case and I am facing a problem.

Essentially we have a Service/Instance of a “class” which has some properties, these properties are actually classes. A Basic example would maybe look like this:

function MySingletonService() {
  function SomeClass(...) {
    ...
  }
  SomeClass.parse= function() {
    ...
    return new SomeClass(...);
  };
  this.SomeClass = SomeClass;
}
angular.module(...).service('MySingletonService', MySingletonService);
// Inside a RUN call here, for demonstrative purposes in this issue, it would be used as:
// The actual use in the legacy project happens in other services, directives, components etc.
angular.run(['MySingletonService', function(service) {
  var instance1OfClass = new service.SomeClass();
  var instance2OfClass = new service.SomeClass();
  var instance3OfClass = service.SomeClass.parse(...);
}]);

But I am a bit at a loss on how to document the “MySingletonService” in a typescript .d.ts file. Normally I would just declare classes and interface for the service, like so:

export declare class SomeClass {
  constructor(...);
  static parse(...):SomeClass;
}

export declare interface IMySingletonService {
  SomeClass: ???;
}

If I define it as SomeClass: SomeClass, then obviously we see it as an Instance of that class, but here it’s the actual class that is assigned to the Property. As such, I have not been able to find an example that looks like what I have.

Searching for just that has proven a bit hard as it basically ends up with examples of how to define the property as an instance if the class, which is straight forward.


It looks like I can get fairly close if I accept having to “Double Document” my code like so:

export declare class SomeClass {
  constructor(...);
  static parse(...):SomeClass;
}

export declare interface IMySingletonService {
  SomeClass: {
    new (...): SomeClass;
    parse(...): SomeClass;
  };
}

But isn’t there a better way?

Answer

typeof SomeClass

The type SomeClass describes an instance of SomeClass. In order to describe the class itself you should use typeof SomeClass.

This type includes the constructor and all static methods. So you can call new service.SomeClass() and also service.SomeClass.parse().

declare class SomeClass {
    constructor();
    static parse(): SomeClass;
}

export declare interface IMySingletonService {
    SomeClass: typeof SomeClass;
}

declare const service: IMySingletonService;

const obj = new service.SomeClass(); // obj has type SomeClass

const parsed = service.SomeClass.parse(); // parsed has type: SomeClass

Typescript Playground Link

Per the docs (previous version):

Next, we then use the class directly. Here we create a new variable called greeterMaker. This variable will hold the class itself, or said another way its constructor function. Here we use typeof Greeter, that is “give me the type of the Greeter class itself” rather than the instance type. Or, more precisely, “give me the type of the symbol called Greeter,” which is the type of the constructor function. This type will contain all of the static members of Greeter along with the constructor that creates instances of the Greeter class. We show this by using new on greeterMaker, creating new instances of Greeter and invoking them as before.

Leave a Reply

Your email address will not be published. Required fields are marked *