TypeScript: custom class extending Array

Viewed 1060

I'm having some troubles with a custom class extending Array. I basically just want to add a few helper methods on the Array class, here I'm adding a logMe method, with a prefix to the array.

class AAA extends Array {
  _prefix: string;

  constructor(prefix: string, values: number[]) {
    console.log('AAA contructor', values);

    // Use hack
    // https://stackoverflow.com/questions/35673043/extending-array-from-typescript
    super();
    this.push(...values);

    this._prefix = prefix;
  }

  logMe() {
    console.log('The prefix is:', this._prefix);
    console.log('The values are:');
    this.map(x => x * 2).forEach(console.log);
  }
}

And here's my test:

const a = new AAA('PREFIX-A', [1, 2, 3]);
a.logMe();

Expected result:

AAA contructor [ 1, 2, 3 ]
The prefix is: PREFIX-A
The values are: 1, 2, 3

Actual result:

AAA contructor [ 1, 2, 3 ]
The prefix is: PREFIX-A
AAA contructor undefined

/Users/amaurymartiny/Workspaces/test-array/a.ts:7
    this.push(...values);
         ^
TypeError: Cannot read property 'Symbol(Symbol.iterator)' of this.push
    at new AAA (/Users/amaurymartiny/Workspaces/test-array/a.ts:7:10)
    at AAA.map (<anonymous>)
    at AAA.logMe (/Users/amaurymartiny/Workspaces/test-array/a.ts:13:41)
    at Object.<anonymous> (/Users/amaurymartiny/Workspaces/test-array/a.ts:18:3)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Module.m._compile (/Users/amaurymartiny/Workspaces/test-array/node_modules/ts-node/src/index.ts:439:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Object.require.extensions.(anonymous function) [as .ts] (/Users/amaurymartiny/Workspaces/test-array/node_modules/ts-node/src/index.ts:442:12)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)

This is quite strange to me, why is the constructor called again when I call this.map?

TypeScript 3.1.3. NodeJS 10.12.0. I already implemented this related thread.

1 Answers

Try this. It worked for me.

class Collection extends Array<xxx> {    
   constructor(documents: Array<xxx> | number) {
      if(documents instanceof Array) super(...documents);
      else super(documents);
      Object.setPrototypeOf(this, Object.create(Collection.prototype));
   }      
   public get aMethodUsingMapAsExample(): string[] {
      // this.map invokes the constructor with the length passed in as argument
      var types = this.map(e => e.documentType);
      types = types.sort();
      return types;
   }  
}

explanation: native array constructor has 2 overloads. You have to support both of them in your extending class. This is because this.map, behind the scenes, creates a new array, thereby invoking the constructor with the length of the array.

  • ArrayConstructor(...items: any[]): Here you must call the spread operator for the array to be constructed correctly
  • ArrayConstructor(arrayLength: number). Calling the spread operator on object of type number will throw an exception.
Related