Willem van Gerven
Willem van Gerven

Reputation: 1587

Type is not assignable to generic type

Why doesn't this compile?

interface ITest { ... }    

class Test implements ITest { ... }

class Factory<T extends ITest> {
    constructor(private _Test: typeof Test) { }

    create(): T {
        return new this._Test();
    }
}

It gives Type 'Test' is not assignable to type 'T'. (property) Factory<T extends ITest>._Test: new () => Test.

How I could make it work is by either

create(): ITest

or through

private _Test: any

Both would not really be communicating (code-wise) what I am after. Any way in which I could make it work?

Upvotes: 1

Views: 3511

Answers (1)

Rico Kahler
Rico Kahler

Reputation: 19222

Why doesn't this compile?

It doesn't compile because the type of _Test is typeof Test. That can be confusing so let me try to break that down:


Typescript add "types" to the world of javascript giving you a way to say "I expect this object to have all these properties and operations available to them.". In an essence, this is all a type system does.

Typescript has the typeof operator which acts like a type query as a way to get the "type" from an instantiated object when used on the right side of a type annotation (the :). The typeof gives you a way to say "whatever properties and operations that this object has, I want to capture those and reuse them to check the type of another object".

Here is an example of using this typeof operator.

let myObject = {
    a: 'a string',
    b: 5,
    c: false
};

// notice that the `typeof` operator is on the right of the `:`
function takesATypeOfMyObject(obj: typeof myObject) {
    // highlight over `a`, `b`, or `c` to see their type
    obj.a // (property) a: string
    obj.b // (property) b: number
    obj.c // (property) c: boolean
}

myObject is a normal javascript object literal. The function takesATypeOfMyObject is a function with one parameter obj with the type annotation of typeof myObject. This function is saying that is can take in any object with the same properties as the object myObject.

This is not to be confused with javascript's typeof operator that only returns a string. Typescript's typeof operator is part of it's type system. Remember that when typescript is compiled to javascript, the types and type annotations go away.


Another important thing to understand is how classes in typescript work. Classes are kind of a doubled-edged-sword. In typescript, classes act as both:

  • a type - as in a definition of properties and operations you can perform on an object. As well as
  • a constructor - as in a concrete function you can call with new to get the mentioned type

It might be tempting to using the type annotation typeof MyClass but this is most likely not what you intended.

Consider the example below:

// define a class Animal
// this creates the type definition of `Animal` as well as
// the constructor to create an `Animal`
class Animal {
    makeNosie() { return 'grrr'; }
}

// use the constructor to create an object of type `Animal`
let myAnimal = new Animal();
// now `myAnimal` has an inferred type of `Animal`

// declare an object to of type `Animal` but instead of using
// the `Animal` constructor, just define an object literal that
// conforms to the type of Animal
let dog: Animal = {
    makeNosie: () => 'woof'
};
// the type of dog has been declared explicitly and typescript
// just checks to see if it conforms to the type

So hopefully you see that if you want an object to conform to the type that a class creates, you don't use the typeof operator, you just say "Animal", not typeof Animal.

But that brings us to the question: what if you did that?

Remember that the typeof operator tries to capture the type to be reused. Since classes define both a type and a newable function to create an object of that type, what typeof Animal is actually doing is querying the type of the constructor of Animal.


Okay so now we can finally dig in your code to understand why it isn't compiling. Below is the original code you pasted:

interface ITest { }    

class Test implements ITest { }

class Factory<T extends ITest> {
    constructor(private _Test: typeof Test) { }

    create(): T {
        return new this._Test();
    }
}

Take a look at the constructor of Factory. What you're saying with this code is that "The constructor of my class Factory takes any Object that conforms to the constructor of Test. That's one pretty complicated type (and probably not what you intended).

Instead try this code:

interface ITest { }    

class Test implements ITest { }

class Factory<T extends ITest> {
    constructor(private _Test: new() => T) { }

    create(): T {
        return new this._Test();
    }
}

The type description of _Test has been changed to new() => T which is saying that "the constructor of Factory takes in any newable function that returns a the type T".

Hopefully that's what you've intended.

I hope I've help you solved your problem as well as show you how powerful typescript is. Typescript is trying to do a very ambitious and crazy thing: add full types to any possible javascript. I think they're doing a great job.

Upvotes: 6

Related Questions