Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extend String Prototype and use it next, in Typescript?

Tags:

typescript

I am extending String prototype chain with a new method but when I try to use it it throws me an error: property 'padZero' does not exist on type 'string'. Could anyone solve this for me?

The code is below. You can also see the same error in Typescript Playground.

interface NumberConstructor {     padZero(length: number); } interface StringConstructor {     padZero(length: number): string; } String.padZero = (length: number) => {     var s = this;     while (s.length < length) {       s = '0' + s;     }     return s; }; Number.padZero = function (length) {     return String(this).padZero(length); } 
like image 715
Rafael 'BSIDES' Pereira Avatar asked Oct 05 '16 14:10

Rafael 'BSIDES' Pereira


People also ask

How do you use a prototype in TypeScript?

Prototype is a creational design pattern that allows cloning objects, even complex ones, without coupling to their specific classes. All prototype classes should have a common interface that makes it possible to copy objects even if their concrete classes are unknown.

What is string prototype in JavaScript?

The prototype is a property available with all JavaScript objects. The prototype property allows you to add new properties and methods to strings.


1 Answers

This answer applies to TypeScript 1.8+. There are lots of other answers to this sort of question, but they all seem to cover older versions.

There are two parts to extending a prototype in TypeScript.

Part 1 - Declare

Declaring the new member so it can pass type-checking. You need to declare an interface with the same name as the constructor/class you want to modify and put it under the correct declared namespace/module. This is called scope augmentation.

Extending the modules in this way can only be done in a special declaration .d.ts files*.

//in a .d.ts file: declare global {     interface String {         padZero(length : number) : string;     } } 

Types in external modules have names that include quotation marks, such as "bluebird".

The module name for global types such as Array<T> and String is global, without any quotes. However, in many versions of TypeScript you can forego the module declaration completely and declare an interface directly, to have something like:

declare interface String {         padZero(length : number) : string; } 

This is the case in some versions pre-1.8, and also some versions post-2.0, such as the most recent version, 2.5.

Note that you cannot have anything except declare statements in the .d.ts file, otherwise it won't work.

These declarations are added to the ambient scope of your package, so they will apply in all TypeScript files even if you never import or ///<reference the file directly. However, you still need to import the implementation you write in the 2nd part, and if you forget to do this, you'll run into runtime errors.

* Technically you can get past this restriction and put declarations in regular .ts files, but this results in some finicky behavior by the compiler, and it's not recommended.

Part 2 - Implement

Part 2 is actually implementing the member and adding it to the object it should exist on like you would do in JavaScript.

String.prototype.padZero = function (this : string, length: number) {     var s = this;     while (s.length < length) {       s = '0' + s;     }     return s; }; 

Note a few things:

  1. String.prototype instead of just String, which is the String constructor, rather than its prototype.
  2. I use an explicit function instead of an arrow function, because a function will correctly receive the this parameter from where it's invoked. An arrow function will always use the same this as the place it's declared in. The one time we don't want that to happen is when extending a prototype.
  3. The explicit this, so the compiler knows the type of this we expect. This part is only available in TS 2.0+. Remove the this annotation if you're compiling for 1.8-. Without an annotation, this may be implicitly typed any.

Import the JavaScript

In TypeScript, the declare construct adds members to the ambient scope, which means they appear in all files. To make sure your implementation from part 2 is hooked up, import the file right at the start of your application.

You import the file as follows:

import '/path/to/implementation/file'; 

Without importing anything. You can also import something from the file, but you don't need to import the function you defined on the prototype.

like image 172
GregRos Avatar answered Sep 28 '22 00:09

GregRos