3

I know how to extend array for any type:

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);
  }
}

Source: Extending Array in TypeScript

But is there also a way to extend the array only for a specific type?. Like only for arrays of type User -> Array<User>.

I want to create a extend method, like .toUsersMap() and this method should not be displayed for arrays, which have not the type User.

Nils Reichardt
  • 3,195
  • 2
  • 18
  • 28

2 Answers2

3

You can achieve similar behaviour:

type User = {
  tag: 'User'
}

interface Array<T> {
  toUsersMap: T extends User ? (elem: T) => Array<T> : never
}

declare var user: User;

const arr = [user]

arr.toUsersMap(user) // ok

const another_array = [{ notUser: true }]

another_array.toUsersMap() // This expression is not callable

If T parameter does not extends User, TS will disallow using toUsersMap

Playground

2

I don't think there's a way to completely suppress the IntelliSense prompting for toUsersMap() on Arrays, but you can definitely make it a compiler error to call arr.toUsersMap() unless arr is assignable to Array<User>. One way to do this is to give the toUsersMap() method a this parameter:

interface Array<T> {
  toUsersMap(this: Array<User>): Map<string, User>;
}

Now the compiler will require that toUsersMap() only be called with a this context of something assignable to Array<User>:

interface User {
  username: string;
}
const arr = [{ username: "foo" }, { username: "bar" }];
arr.toUsersMap() // okay, no error

const another_array = ["hello", 123];
another_array.toUsersMap() // error, 
//~~~~~~~~~~~ <--
// The 'this' context of type '{ notUser: boolean; }[]' is 
// not assignable to method's 'this' of type 'User[]'

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360