23

I am working in an angular 2 cli project in which I have to create a definition of a plugin, because it doesn't exists its typed. This plugin depends of a main library that has already its own typed and it works.

Anyway, I have two files the main one with

LIBRARY TYPES FILE A

export class A extends B {
    constructor(...);
    methodX(): void;
}

And I would need to add a new method for my plugin so my class would be like

export class A extends B {
        constructor(...);
        methodX(): void;
        methodY(): void;
    }

The point is that I need to add it in a separate file. The problem is adding a method to an existent class without creating a new one

If I put

PLUGIN TYPES FILE B

export class A extends B {
    constructor(...);
    methodX(): void;
}

or

PLUGIN TYPES FILE B

export class A extends B {
        constructor(...);
        methodX(): void;
        methodY(): void;
}

It doesn't work, does anyone how can I achieve overwriting a class or extending it with a new method that?

Thanks

aleung
  • 9,848
  • 3
  • 55
  • 69
ackuser
  • 5,681
  • 5
  • 40
  • 48
  • Not sure from your question. It is not clear. Do you need Mixins? https://www.typescriptlang.org/docs/handbook/mixins.html – Amit Mar 24 '17 at 12:45
  • If you ask how "extending it", it is simple as `class C extends A`. But I think you mean "adding a new method without changing the class name". If that is right, please clarify your question. – Bünyamin Sarıgül Mar 24 '17 at 12:47
  • 2
    also, why not just extend the class and add the method? – toskv Mar 24 '17 at 12:47
  • @toskv because sometimes you want to extend capabilities of a commonly used class. For example, adding `flatMap` to `Array` if you're using NodeJS < 11 (`ts-node` is Node in version 8). – Jezor Mar 19 '19 at 11:36

3 Answers3

25

The "Declaration Merging > Module Augmentation" section from the TypeScript docs seems to offer the solution:

https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation

In your case, if class A is exported from file1.ts, and you want to add methodY() to that class within a different module file2.ts, then try this:

//// file1.ts
export class A extends B {
    constructor(...);
    methodX(): void;
}

//// file2.ts
import { A } from "./file1";
declare module "./file1" {
    interface A {
        methodY(): void;
    }
}
A.prototype.methodY = function() {}
Serban Stokker
  • 384
  • 3
  • 6
  • 3
    Btw adding a new method to a class within the SAME file would have been done like so: `class A {}; interface A { methodY(): void; }; A.prototype.methodY = function () {};` – Serban Stokker Apr 28 '20 at 16:52
  • 1
    This is the one solution that actually worked! Thanks! – panzi Feb 11 '21 at 18:34
  • to add to the first comment, `declare module './file1'` still works inside of `file1.ts` You must do this for the compiler to understand your calls to `methodY` in other places. Read more here on [Module Augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) – manroe Jul 06 '23 at 18:47
18

You could do it by making an interface with the new method and modifying the prototype.

Something like this:

class B { }

class A extends B {
    constructor() {
        super();
    }
    methodX(): void { };
    methodY(): void { };
}


interface B {
    newMethod(): void;
}

B.prototype.newMethod = function () { console.log('a') };

This allows you do have proper typing when doing.

new A().newMethod();

I made a playground example here.

toskv
  • 30,680
  • 7
  • 72
  • 74
  • Do you need to use an interface? I added the function to the class itself, and it worked. – Bünyamin Sarıgül Mar 24 '17 at 12:58
  • I had to add it otherwise the interface of B doesn't change and it wouldn't let me change the prototype of B. – toskv Mar 24 '17 at 13:00
  • it might depend where you make the change. in your example you do it inside the class itself. I do it from the outside. I think the OP wasn't allowed to change the code. :) – toskv Mar 24 '17 at 13:02
  • nope, it's not that.. I think it's just plnkr not complaining about it. :) It might have something to do with in browser compiler. – toskv Mar 24 '17 at 13:04
  • I didn't think he was asking to change interface B, but it makes sense to do it this way. My answer becomes unnecessary then, sorry for duplicate answer :) – Bünyamin Sarıgül Mar 24 '17 at 13:05
  • well.. I agree he wasn't explicitly asking for it but it is implicit, if you change the class, you change it's type as well. Luckily with typescript's open interfaces it's easy to do it. :) – toskv Mar 24 '17 at 13:07
  • @BünyaminSarıgül as for your answer, it's a good answer well written too. :) – toskv Mar 24 '17 at 13:10
  • Awesome!!!!Thx it works perfectly. I am working with leaflet and plugins and I was able to integrate through the interface without modifying the main class – ackuser Mar 24 '17 at 13:26
  • This thing has a problem. Typescript thinks that the new property is own to the object, and don't know that it is from the prototype. This may cause problems when you copy the object with the spread operator. – Gabriel Machado Sep 01 '21 at 18:45
  • In general mixing class instances and the spread operator will lead to issues. If you plan on making copies of class instances you're probably better off using a clone method or a copy constructor. – toskv Sep 03 '21 at 07:53
3

You can do it directly as A.prototype.functionName = function(){...}

Here is a plunker: http://plnkr.co/edit/6KrhTCLTHw9wjMTSI7NH?p=preview

Bünyamin Sarıgül
  • 3,031
  • 4
  • 30
  • 55
  • This should be the accepted answer. I've found in VSCode with typescript it won't accept the 'A.prototype.functionName' notation but will accept 'A.prototype["functionName"]'. – lakam99 May 04 '22 at 16:29