In what follows it is important to understand the difference in TypeScript between types, which only exist at compile time and are erased when code is compiled to JavaScript; and values, which are emitted to JavaScript and therefore exist at runtime. A value can have a type, but they are not the same thing and they exist in different syntactic contexts in TypeScript code. In something like
const foo: Bar = baz<Qux>();
Bar and Qux are types and foo and baz are values. When that code is compiled to JavaScript it will probably become
const foo = baz();
Because they exist in different syntactic contexts, you can actually have types and values use the same names without colliding with each other. So you could have:
const A: B = B<A>();
where the first B and the second A are types, and the first A and the second B are values. There is no necessary relationship between the value named A and the type named A. The code above is exactly analogous to const foo: Bar = baz<Qux>();, things are just renamed. This might be confusing to a human being but the compiler is not confused and knows from syntax context which ones are the values and which ones are the types. The above would compile to
const A = B();
where the values remain and the types are gone.
For your example code the relevant definitions are located in Angular's event_emitter.ts. If you hover over the imported EventEmitter in an IntelliSense-enabled IDE like
The TS Playground, you should see something like this:
/*
(alias) interface EventEmitter<T>
(alias) const EventEmitter: {
new (isAsync?: boolean | undefined): EventEmitter<any>;
new <T>(isAsync?: boolean | undefined): EventEmitter<T>;
readonly prototype: EventEmitter<any>;
}
*/
That means there are two separate things named EventEmitter. The first is interface EventEmitter<T>, a named generic type. If this were the only declaration of EventEmitter then you would not be able to instantiate it. The second is const EventEmitter, a named value, the type of which hose type contains some construct signatures, one of which is generic. If you call the generic construct signature, then the returned value is of type EventEmitter<T>;
So in the line
@Output() sharedData: EventEmitter<string> = new EventEmitter<string>;
// ^ ^
// (alias) interface EventEmitter<T> |
// (alias) new EventEmitter<string>(isAsync?: boolean | undefined): EventEmitter<string>
the first EventEmitter<string> is a generic interface type instantiated with string, while the latter EventEmitter<string> is part of a call to a generic constructor function.
Note that when you have a class declaration, it introduces both a named type (the class instance type) and a named value (the class constructor value) with the same name. This is useful, but it does give the erroneous impression that types and values that share names are inherently related to each other.
Anyway, a class declaration's named value and type relationship is essentially the same as the relationship with Angular's EventEmitter. Indeed, they could have provided essentially the same typings with something like:
declare class EventEmitter<T = any> {
constructor(isAsync?: boolean | undefined);
new(isAsync?: boolean): EventEmitter<T>;
emit(value?: T): void;
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void):
Subscription;
subscribe(observerOrNext?: any, error?: any, complete?: any): Subscription;
}
And then the code like
const xxx: EventEmitter<string> = new EventEmitter<string>;
// ^ ^
// class EventEmitter<T = any> |
// constructor EventEmitter<string>(isAsync?: boolean | undefined): EventEmitter<string>
would do the same thing except that IntelliSense would use class and constructor to refer to the type and value instead of interface and new.
You could then ask, if those are the same, why didn't they use a class declaration instead of an interface/const pair? That's a good question but probably out of scope here. In general there are things you can't adequately describe with a class declaration and in those cases a type/value pair would be necessary, so perhaps this was done to give the most flexibility.
Playground link to code
new XXXyou are referring to a value namedXXX, not a type namedXXX, even though there might be such a type in scope. SoEventEmitteris presumably the name both of a constructor value and of an interface type. This sort of thing happens automatically withclassdeclarations (class Foo {}creates both a value namedFooand a type namedFoo) but it can be done separately (likeinterface Bar {}and thendeclare const Bar: new () => Bar).importstatements? If you can do that that I could pinpoint exactly where the two definitions ofEventEmitterare and why things behave as they behave. Right now, though, all I have is thatEventEmitteris not defined. If you make such an edit and want me to take another look, please mention @jcalz in a comment to notify me.