Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instantiating TypeScript generic class without specifying type

If I create a generic class

class Generic<T> {
     prop: T;
}

It won't allow me to type something without specifying T in the type

// Generic type 'Generic<T>' requires 1 type argument(s).
const val: Generic = new Generic(); 

But if I use type inference, it doesn't complain, it lets me instantiate it

const val = new Generic();
// field prop is not of type any, we can't access any properties on it
// val.prop.anything -> Compiler error
// val.prop.toString() -> No error, is it a string?
// val.prop.length -> Compiler error, not a string, just assumes that everything has a toString (based on boxing)

Is this behavior specified? What is the reasoning behind this?

Background

Angular2 has an EventEmitter which requires a type of the argument for the event. However, for some events, you don't pass any arguments, in which case we had been using EventEmitter<void>. However, I just noticed that you can just define emitters without specifying type and new EventEmitter() works. The drawback of this approach is that the compiler won't complain if you pass an argument to emitter.emit('something'). This is not what I'm interested in, just background so readers can understand where the question came from.

Playground https://www.typescriptlang.org/play/#src=class%20Generic%3CT%3E%20%7B%0D%0A%20%20%20%20prop%3A%20T%3B%0D%0A%7D%0D%0A%0D%0Aconst%20val%3A%20Generic%20%3D%20new%20Generic()%3B%0D%0A%0D%0Aconst%20val2%20%3D%20new%20Generic()%3B%0D%0A%0D%0Aval2.prop.test%20%3D%201%3B

like image 639
Juan Mendes Avatar asked Oct 22 '25 04:10

Juan Mendes


1 Answers

The following two statements are equivalent...

const val: Generic<{}> = new Generic();
const val2 = new Generic();

In the first case, the type argument that is omitted on the right-hand side is inferred from the type on the left-hand side.

In the second case, you end up with an object type, because nothing more specific can be inferred.

The rule here is that the type of the variable must have the generic type parameter satisfied. When you use const val: Generic = new Generic(); the type parameter is not satisfied - you can't ask for it to be inferred because you have decided to annotate the variable.

So the two allowable scenarios are:

  1. Specify the (entire) type yourself with a type annotation
  2. Allow the (entire) type to be inferred by the compiler

To make your later example work, you have two options.

Option 1... if you really don't want to limit the type, go dynamic...

const val2 = new Generic<any>();
val2.prop.test = 1;

Or... Option 2... if you do want to limit the type, specify it.

const val2 = new Generic<{ test: number }>();
val2.prop.test = 1;

And finally, in many contexts, you don't need to specify the type argument as it can be inferred contextually.

like image 161
Fenton Avatar answered Oct 23 '25 21:10

Fenton