AndyJ AndyJ - 1 year ago 175
TypeScript Question

How do I extend a TypeScript class definition in a separate definition file?

I have a JS library called leaflet which has an existing TypeScript definition file.

I wish to use a plugin which extends some of the objects in leaflet with an extra function.

In the existing TypeScript definition file the objects are defined as classes rather than interfaces.


declare module L {
function circleMarker(latlng: LatLng, options?: PathOptions): CircleMarker;

export class CircleMarker extends Circle {
constructor(latlng: LatLng, options?: PathOptions);
setLatLng(latlng: LatLng): CircleMarker;
setRadius(radius: number): CircleMarker;
toGeoJSON(): any;

If I try and define it a second time in a separate file then I get an error about "Duplicate Identifier 'CircleMarker'.".

declare module L {
export class CircleMarker {
bindLabel(name: string, options: any): CircleMarker;

This makes sense as it's a class and not an interface, but that being the case is there a way to extend this class definition without changing the original definition file?

The base definition file is pulled in from DefinitelyTyped via nuget so I have a very strong desire not to make any changes to it as it'll make updating much more awkward/prone to failure.

Answer Source

If you don't control the original definition file, and can't make adjustments to it, then unfortunately, what you're trying to do isn't supported currently in TypeScript. An interface in TypeScript is the only construct that allows reasonable extensions as it is only a compile-time/syntax check and not a run-time operation.

You cannot extend a class in TypeScript with new functionality using only TypeScript (and expecting code-completion/Intellisense to work as expected). You could of course add the functions to the prototype for the CircleMarker class, but they would be unavailable to Intellisense and would fail to compile unless you use a type assertion.

Instead of using any, you should be able to use an interface with the type assertion:

declare module L {
    export interface CircleMarkerEx {
        bindLabel(name: string, options: any): CircleMarker;


var cm = <L.CircleMakerEx> circle.bindLabel("name", {});

Thankfully, it doesn't add any run-time overhead, just a bit of extra typing (pun intended!).

There have been suggestions for things like "mix-ins" on CodePlex, but they have not been implemented. Even the mix-in suggestions would not be entirely straightforward to use, and wouldn't work well for libraries that weren't entirely written in TypeScript (as it would be too easy to have JavaScript code that simply could not be safely constructed for example with a mix-in).