BrunoLM
BrunoLM

Reputation: 100381

How can I check if element is an instanceof U?

I want to add elements of type any to an array and later get the elements from this array that are numbers:

function OfType<T, U>(list: T[]) : U[]
{
    var result: U[] = [];

    list.forEach(e => {
        // I want to check if e is of type U
        //if (typeof(e) === typeof(U)) // ERROR: doesn't work
            result.push(<U><any>e);
    });

    return <any[]>result;
}


var list: any[] = [];
list.push("A");
list.push(2);

var result = OfType<any, number>(list);

alert(result.toString());

But it doesn't allow me to check the type of the elements against a generic type.

Is there a way to accomplish this?

Upvotes: 15

Views: 27034

Answers (4)

Eyal Alalouf
Eyal Alalouf

Reputation: 1

Following BrunoLM answer, I would just change the Contructor type to:

type Constructor<T> = abstract new (...args: any[]) => T;

This allows also abstract classes to play with this. Note, that the constructor of the abstract class needs to be public. Otherwise, the compiler will complain. This is true for any class using this type of solution.

Upvotes: 0

Stephen Weatherford
Stephen Weatherford

Reputation: 191

You can currently do it much better this way (TypeScript 3.9):

// tslint:disable-next-line: no-any
type Constructor<T> = new (...args: any[]) => T;

export function ofType<TElements, TFilter extends TElements>(
    array: TElements[], 
    filterType: Constructor<TFilter>
): TFilter[] {
    return <TFilter[]>array.filter(e => e instanceof filterType);
}

Example usage:

class ClassA { }
class ClassB { }

const list: ClassA[] = [new ClassA(), new ClassB()];
const filteredList = ofType(list,  ClassB);

Upvotes: 19

BrunoLM
BrunoLM

Reputation: 100381

As Judah pointed out it is unlikely to be possible using generic types alone. I found a workaround where I send in one more parameter with the type...

function OfType<T, U>(list: T[], arg: Function) : U[]
{
    var result: U[] = [];

    list.forEach(e => {
        // extract the name of the class
        // used to match primitive types
        var typeName = /function\s*([^(]*)/i.exec(arg+"")[1].toLocaleLowerCase();

        var isOfType = typeof(e) === typeName;

        // if it is not primitive or didn't match the type
        // try to check if it is an instanceof
        if (!isOfType)
        {
            try {
                isOfType = (e instanceof arg)
            }
            catch (ex) { }
        }

        if (isOfType)
            result.push(<U><any>e);
    });

    return <any[]>result;
}

Usage:

var numbers = OfType<any, number>(list, Number);
var foos = OfType<any, Foo>(list, Foo);

alert("Numbers: " + numbers);
alert("Foos: " + foos);

Little redundancy, if someone know a way to remove this redundancy please leave a comment or edit this code.

Or, for primitive types only I could use filter as Judah mentioned.

Upvotes: 2

Judah Gabriel Himango
Judah Gabriel Himango

Reputation: 60051

Javascript typeof works on instances of objects, not on types themselves. (After all, TypeScript generics disappear in the compiled JavaScript.)

You'll need to get an instance of U, call typeof on that, and compare it to typeof(e).

Keep in mind, type information in JavaScript is not rich like in the .NET framework. typeof(myCustomObject) will return 'object', even if the object is an instance of class Foo.

In your case, you're trying to build an .OfType method that will filter an array to only numbers. You could write it like this:

var list: any[] = [];
list.push("A");
list.push(2);
var numbers = list.filter(e => typeof(e) === "number");

Upvotes: 1

Related Questions