What are you trying to do? As @NitzanTomer points out you can't specialize generics like that in TypeScript, where Array<MyObject>
has a method isEmpty()
, but Array<string>
does not. Depending on how you plan to use it, there are things that get you close.
Option 1: Extend Array
prototype as you requested (not recommended)
The closest I can get to what you specifically asked for is to require the this
parameter of isEmpty()
to be of type Array<MyObject>
:
declare global {
interface Array<T> {
isEmpty(this: Array<MyObject>): boolean;
}
}
// note the error below
Array.prototype.isEmpty = function() {
if (this.length == 0) {
return true;
} else {
return this.some((c) => c != null &&
c != '' // error! c has to be MyObject, not string
);
}
};
(As an aside, your implementation of isEmpty()
has an error when you compare an array element, now required to be MyObject
, to a string
value. The good news is that TypeScript is alerting you of this so you can fix it.)
You can now go ahead and call isEmpty()
on arrays of MyObject
:
declare let myObject1: MyObject;
declare let myObject2: MyObject;
const arrayOfMyObject: MyObject[] = [myObject1, myObject2];
arrayOfMyObject.isEmpty(); // okay
Now, the isEmpty()
method exists on all arrays, but trying to call it on an array of a different type will give you an error:
const arrayOfStrings: string[] = ['a', 'b'];
arrayOfStrings.isEmpty(); // error, string is not MyObject
So that might work for you.
That being said, extending native prototypes like Array
is almost always considered a bad idea.
Option 2: Extend selected Array
instances with new method
A better idea would be to create a brand new class with the extra method, or to take individual Array
instances and add the method to them without polluting the Array
prototype. Here's a way to do the latter:
interface MyObjectArray<T extends MyObject> extends Array<T>{
isEmpty(): boolean;
}
const isEmpty = function(this: Array<MyObject>) {
if (this.length == 0) {
return true;
} else {
return this.some((c) => c != null &&
c != '' // error! c is MyObject, not string
);
}
};
function toMyObjectArray<T extends MyObject>(arr: Array<T>): MyObjectArray<T> {
const ret = arr as MyObjectArray<T>;
ret.isEmpty = isEmpty.bind(ret);
return ret;
}
Now we have a new interface called MyObjectArray
which includes the extra method. And if you have an existing instance of MyObject[]
, you can convert it to MyObjectArray
using the toMyObjectArray()
function, which adds the isEmpty()
method to it. Then you can use it like this:
declare let myObject1: MyObject;
declare let myObject2: MyObject;
const arrayOfMyObject = [myObject1, myObject2];
arrayOfMyObject.isEmpty(); // error, no such method
const myObjectArray = toMyObjectArray(arrayOfMyObject); // convert
myObjectArray.isEmpty(); // okay
// can't convert an array of anything else
const arrayOfStrings = toMyObjectArray(['a', 'b', 'c']);
There's an extra step of calling a conversion function every time you need to use isEmpty()
, which might make this less desirable for you.
Option 3: Use standalone isEmpty()
function, not method
In fact, if you are not going to mess with Array.prototype
, and you have to call a function on the array instance, you might as well just skip the extra interface and use a standalone isEmpty()
instead of bothering with methods:
const isMyObjectArrayEmpty = function(arr: Array<MyObject>) {
if (arr.length == 0) {
return true;
} else {
return arr.some((c) => c != null &&
c != '' // error! c is MyObject, not string
);
}
};
declare let myObject1: MyObject;
declare let myObject2: MyObject;
const arrayOfMyObject = [myObject1, myObject2];
isMyObjectArrayEmpty(arrayOfMyObject); // okay
const arrayOfStrings = ['a', 'b', 'c'];
isMyObjectArrayEmpty(arrayOfStrings); // error
Those are the options as I see them.