57

Possible Duplicate:
How does prototype extend on typescript?

I am currently learning TypeScript, and would like to know how it is possible to add functionality to existing objects. Say I want to add an implementation for Foo to the String object. In JavaScript I would do this:

String.prototype.Foo = function() {
    // DO THIS...
}

Understanding that TypeScript classes, interfaces and modules are open ended led me to try the following, without success

1. Reference the JavaScript implementation from TypeScript

    JavaScript:

    String.prototype.Foo = function() {
        // DO THIS...
    }

    TypeScript:

    var x = "Hello World";
    x.Foo(); //ERROR, Method does not exist

2. Extend the interface

interface String {
    Foo(): number;
}

var x = "Hello World";
x.Foo(); //Exists but no implementation.

3. Extend the class

class String {
    Foo(): number {
        return 0;
    }
}

// ERROR: Duplicate identifier 'String'

As you can see from these results, so far I have been able to add the method via an interface contract, but no implementation, so, how do I go about defining AND implementing my Foo method as part of the pre-existing String class?

Community
  • 1
  • 1
Matthew Layton
  • 39,871
  • 52
  • 185
  • 313

1 Answers1

75

I have found the solution. It takes a combination of the interface and the JavaScript implementation. The interface provides the contract for TypeScript, allowing visibility of the new method. The JavaScript implementation provides the code that will be executed when the method is called.

Example:

interface String {
    foo(): number;
}

String.prototype.foo= function() {
    return 0;
}

As of TypeScript 1.4 you can now also extend static members:

interface StringConstructor {
    bar(msg: string): void;
}

String.bar = function(msg: string) {
    console.log("Example of static extension: " + msg);
}
Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • 2
    Exactly, that's how it is meant to be. Remember, TypeScript is not a language in itself, it is just a syntactic sugar. So writing something in 'JavaScript style' is a good practice that has no drawbacks whatsoever. – Oleg Mihailik Dec 16 '12 at 20:51
  • 5
    Does this solution really work with **String**? Because I'm doing the _exact_ same thing and get compile errors that the property doesn't exist on the String type. What am I missing? (using tsc 1.4.1) – sebas2day Feb 05 '15 at 13:59
  • @sebas2day yes it works - http://tinyurl.com/ouwh3ln – Matthew Layton Feb 05 '15 at 14:02
  • @series0ne that's interesting. It does compile indeed, but it gives me an error anyway. Very strange... – sebas2day Feb 05 '15 at 14:17
  • 1
    @sebas2day are you targetting an older version of the compiler (i.e. pre 1.4), this has caused me problems in the past. – Matthew Layton Feb 05 '15 at 14:18
  • @series0ne yes you were right, it was using the TSC compiler of "Microsoft SDKs\TypeScript". Solved it now. Thanks – sebas2day Feb 05 '15 at 14:40
  • This doesn't work for me in 1.8 with custom types. TS claims that the prototype doesn't have the added method in the interface (e.g. `foo`), even though the class implements the interface (though it doesn't implement the function in the interface), so I can't implement it (e.g. `prototype.foo = ...`) in the separate file. – Josh Mar 17 '16 at 10:57
  • @Josh, what do you mean by custom types? i.e. a TypeScript class or a JavaScript object for which you have created an ambient declaration? – Matthew Layton Mar 17 '16 at 10:59
  • I mean a TypeScript class that I created that implements the interface, not String, as in the answer, which seems to somehow be both an interface and class at the same time (interfaces don't have prototypes AFAICT). You can "implement" the added function via indexing the class, like `Foo['bar'] = ...`, but that doesn't indicate to TypeScript that the class implements the interface's function so it still throws a compiler error for the class incorrectly implementing the interface. – Josh Mar 17 '16 at 11:08
  • @Josh could you possibly post your code into the TypeScript playground and share so I can see what the error is? – Matthew Layton Mar 17 '16 at 11:10
  • Sure: http://tinyurl.com/gtqwsfr – Josh Mar 17 '16 at 11:14
  • Just to be clear, I'm not saying that your question or answer is invalid. I'm just pointing out that I think it may only work in specific cases (built-in types maybe) and not as a general solution in case others happen upon this as I did and expect it to work. – Josh Mar 17 '16 at 11:23
  • @Josh yes this is for built in types - this sort of thing WILL work for custom types that are implemented in JAVASCRIPT but WILL NOT work for custom types that are built in TYPESCRIPT – Matthew Layton Mar 17 '16 at 11:33
  • @Josh see here... http://tinyurl.com/gsag7gg – Matthew Layton Mar 17 '16 at 11:34
  • 19
    @OlegMihailik Aren't **all** langues just syntactic sugar? – Alex Aug 03 '16 at 09:14
  • Use `Object.defineProperty` instead of adding prototype directly like that – Arijoon May 02 '17 at 22:54
  • @Arijoon why? Okay I know why but the TypeScript compiler doesn't output things like that, and what happens if you're targeting ES3? – Matthew Layton May 02 '17 at 22:55
  • the question now is, how wrap that in a module and import it in other modules ????? – Mohamed Ali Jun 25 '17 at 03:05
  • code above doesnt work( – Rami Chasygov May 19 '18 at 21:20
  • @RamzanChasygov which version of TypeScript are you targeting? – Matthew Layton May 19 '18 at 21:21
  • @series0ne, "2.5.2" – Rami Chasygov May 19 '18 at 21:23
  • @RamzanChasygov that’s unusual. That should definitely work. What errors are you getting? – Matthew Layton May 19 '18 at 21:24
  • 6
    @series0ne, if copypast code above, `[ts] Property 'foo' does not exist on type 'String'.` – Rami Chasygov May 19 '18 at 21:25
  • Are you using foo as a static or instance property? – Matthew Layton May 19 '18 at 21:27
  • 1
    @series0ne, instance, also throws warning `[ts] 'String' is declared but its value is never read.` – Rami Chasygov May 19 '18 at 21:28
  • It sounds like the default lib.d.ts isn’t being imported properly. There is also an issue with TS 2.5 where lib.d.ts isn’t imported properly for certain targets. I would consider updating to the latest version of TypeScript (2.9 now I think) – Matthew Layton May 19 '18 at 21:31
  • @series0ne, updated ts, updated `tsconfig.json` from [typescript](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html), doesnt help( – Rami Chasygov May 19 '18 at 21:46
  • @series0ne, in [playground](https://www.typescriptlang.org/play/) everything works, maybe some kinda error for my os(Ubuntu 17) – Rami Chasygov May 19 '18 at 21:48
  • 22
    In case anyone else has the same problem I did: I used the code here, and found it worked. BUT, if I use it in a module (i.e. with an `export` statement) then I get the error `[ts] Property 'foo' does not exist on type 'String'.` (in both VS Code and the playground) . The fix is to make the interface global: `declare global { interface String { foo(): number; } }`. – Z Smith Nov 10 '18 at 14:26
  • @ZSmith It works. Is this a safe way to extend it? – JeffMinsungKim Sep 23 '19 at 04:37
  • 1
    @JeffMinsungKim I think so. It's given as an example at the very bottom of this page: https://www.typescriptlang.org/docs/handbook/declaration-merging.html – Z Smith Sep 23 '19 at 08:11
  • 1
    @JeffMinsungKim "Is this a safe way to extend it?" - In general it's considered bad practice to extend existing JavaScript objects in this way, and with good reason. There's a lot of literature worth reading on this topic, starting here... https://stackoverflow.com/questions/14034180/why-is-extending-native-objects-a-bad-practice – Matthew Layton Sep 23 '19 at 09:33
  • 1
    @series0ne Thanks a lot :) It was very helpful indeed! – JeffMinsungKim Sep 24 '19 at 01:28