104

How to add a method to a base type, say Array? In the global module this will be recognized

interface Array {
   remove(o): Array;
}

but where to put the actual implementation?

0

6 Answers 6

170

You can use the prototype to extend Array:

interface Array<T> {
    remove(o: T): Array<T>;
}

Array.prototype.remove = function (o) {
    // code to remove "o"
    return this;
}

If you are within a module, you will need to make it clear that you are referring to the global Array<T>, not creating a local Array<T> interface within your module:

declare global {
    interface Array<T> {
        remove(o: T): Array<T>;
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

@FrancoisVanderseypen that could be a pain, I suggest you don't try it. It is easier the way proposed here. But if you are curious: stackoverflow.com/a/14001136/340760
it should be interface Array<T> { remove(o): T[]; } in new version with generics
@SteveFenton Hmm, can you elaborate? I can do such things in regular Node modules, why TypeScript forbids that? Do you know any workarounds?
Definitions must be at the same level in order to take effect, so if you are in a module and you declare interface Array<T> that is a member of the module, i.e. MyModule.Array<T>. That means the global Array<T> is not extended, but a new local interface is created. You have to put extensions in the global scope... I'd suggest putting the interface in a .d.ts file. Do you also need to patch the array definition or is it just the interface?
On typescript 2.4.1 it needs added declare global {} on the interface declaration, otherwise it will trigger error
|
73

declare global seems to be the ticket as of TypeScript 2.1. Note that Array.prototype is of type any[], so if you want to have your function implementation checked for consistency, best to add a generic type parameter yourself.

declare global {
  interface Array<T> {
    remove(elem: T): Array<T>;
  }
}

if (!Array.prototype.remove) {
  Array.prototype.remove = function<T>(this: T[], elem: T): T[] {
    return this.filter(e => e !== elem);
  }
}

4 Comments

This does the trick, expect inside the function this will be any[], not T[]. Does anyone know why? Is there a workaround?
I added an explicit type to the this parameter based on your question. See the "this parameters" section of typescriptlang.org/docs/handbook/functions.html.
So how do you import this into another .ts file?
@Sprotty use import './utils/extensions' e.g. the path to your file with the declaration
24

Adding to Rikki Gibson's answer,

export {}

declare global {
    interface Array<T> {
        remove(elem: T): Array<T>;
    }
}

if (!Array.prototype.remove) {
  Array.prototype.remove = function<T>(elem: T): T[] {
      return this.filter(e => e !== elem);
  }
}

Without the export {}, you will get the TS error:

Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.

4 Comments

How can we import this? Does "import * as array_extensions" work? or Is there any way to import just some of extension functions?
@Saleh Use import "./array_extensions"
I wonder why export {} helps?
@tBlabs Because it makes it an "external module", as the error suggests.
8

From TypeScript 1.6, you can "natively" extend arbitrary expressions like inbuilt types.

What's new in TypeScript:

TypeScript 1.6 adds support for classes extending arbitrary expression that computes a constructor function. This means that built-in types can now be extended in class declarations.

The extends clause of a class previously required a type reference to be specified. It now accepts an expression optionally followed by a type argument list. The type of the expression must be a constructor function type with at least one construct signature that has the same number of type parameters as the number of type arguments specified in the extends clause. The return type of the matching construct signature(s) is the base type from which the class instance type inherits. Effectively, this allows both real classes and "class-like" expressions to be specified in the extends clause.

// Extend built-in types

class MyArray extends Array<number> { }
class MyError extends Error { }

// Extend computed base class

class ThingA {
    getGreeting() { return "Hello from A"; }
}

class ThingB {
    getGreeting() { return "Hello from B"; }
}

interface Greeter {
    getGreeting(): string;
}

interface GreeterConstructor {
    new (): Greeter;
}

function getGreeterBase(): GreeterConstructor {
    return Math.random() >= 0.5 ? ThingA : ThingB;
}

class Test extends getGreeterBase() {
    sayHello() {
        console.log(this.getGreeting());
    }
}

3 Comments

This leads to problems, in that the [] operator fails to behave as expected. stackoverflow.com/questions/33947854/…
The question was how to extend Array.prototype not just class inheritance
@PavelNazarov Whoops! Was there ever any case of Inheritance where there was never a Child extending a Parent? Or put differently; is it not obvious that this Answer tries to inform about a newer, expressive way of extending built-in Types like Array without resorting to the decrepit Prototypical Inheritance? class MyArray extends Array<number> { } . That 1st Line communicates original Intent which is entirely consistent with the Topic. The OP is a Programmer. He only needs to know what's available as expressed in that 1st Line. The Implementation is up to him, right?
7

Extending Array

Here is an example to extend Array and add the remove method to it. This is a JavaScript example

/** @template T */
class List extends Array {
  /**
   * Remove an item from the list and return the removed item
   * @param {T} item
   * @return {T}
   */
  remove(item) {
    const index = this.indexOf(item);
    if (index === -1) {
      throw new Error(`${item} not in list`);
    }
    this.splice(index, 1);
    return item;
  }
}

const arr = new List(1, 2, 3);
console.log(arr.remove(3)); // 3
console.log(arr); // [1, 2]

And this is a TypeScript example.

I've added a constructor and pushed the arguments of it to the array. (Couldn't do it with super(...items)!

class List<T> extends Array {
  constructor(...items: T[]) {
    super();
    this.push(...items);
  }

  public remove(item: T): T {
    const index = this.indexOf(item);
    if (index === -1) {
      throw new Error(`${item} not in list`);
    }
    this.splice(index, 1);
    return item;
  }
}

2 Comments

Just tried it with TypeScript and it worked very well and is very OOP idiomatic.
This is not extending Array, it is 'just' a subclass that adds a remove function. The name List is not well-chosen, as it is not really a 'list', in the general sense typical for naming programming paradigms. A list would suggest something like a linked list, with at least a bit more usage than just an Array with .remove function.
4
class MyArray<T> extends Array<T> {
    remove: (elem: T) => Array<T> = function(elem: T) {
        return this.filter(e => e !== elem);
    }
}
let myArr = new MyArray<string>();
myArr.remove("some");

this works for me with typescript v2.2.1!

2 Comments

This problem with this approach is that now every array has to be of the type MyArray, so it won't work seamlessly with other libraries that return arrays.
The question was how to extend Array.prototype not just class inheritance

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.