moduletypescriptdefinitiontypescript1.4

Extensible Object In Typescript Definition


I have an object type that can be extended in javascript

var myObject = Library.LibraryType();

myObject.myExtension = MyExtension();

As long as the extension adheres to some interface, the LibraryType can do useful things with it.

But how do I express this in typescript definition file? I don't want the users to have to cast everything to <any> all the time.

I wanted to do it with a dictionary. But the compiler complains as all the other members don't adhere to the dictionary definition.

export interface IExtension {
}
export interface IExtensibleLibraryType {
    something: string;
    otherthing: (args: number) => void;

    [ keyOfExtension: string ]: IExtension;
}

It says all the other members are not IExtension.

I would have thought the compiler would have just enforced that the dictionary members are accessed as dictionary mytype['keyOfExtension'] where all the other members could be accessed with the . notation? Why is this not a thing?

Is there any way around this? Or do I just have to tell everyone to cast to any the whole time? Or force the users to extend the interfaces themselves in their own code (best option, but I bet people are more likely to do the former)?

I would really like it if the .d.ts could fully describe the API of the library like a useful addition to the docs.


Solution

  • TypeScript currently has better support for classical OO than for the approach you're using.

    class LibraryType {
        constructor() {
            // equivalent of your Library.LibraryType() function
        }
    
    }
    
    class MyExtendedVersion extends LibraryType {
        extension: blah
    }
    

    Sadly this requires you to change how your library works. Instead of exporting a factory function, you have to export a class. And internally your class's code will have to use this to access its own instance data.

    When migrating my own large-ish project to TS, I tried the classical conversion for a few files and quickly gave up with it. It was a "rewrite" experience, which is bad because I need to make bug fixes in the previous version and merge them into the TS version. It's much easier to migrate to TS if all you have to do is add a few type annotations here and there. So I use interfaces, $.extend and casting (type assertions) to avoid the need to rewrite.

    There's a request for a language feature here that might help us in the future, but it's not on the roadmap so far.