1

In the following example, an instance of EventEmitter interface is being created however, I believe that the interfaces can't be instantiated but the classes.

@Output() notify: EventEmitter<string> = new EventEmitter<string>();

Complete code below:

import { Component, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styles: [
  ]
})
export class ChildComponent {

  @Output() sharedData: EventEmitter<string> = new EventEmitter<string>;

  shareData() {
    this.sharedData.emit('Hello, world!!!');
  }

}
6
  • 1
    Indeed, interfaces cannot be instantiated. But EventEmitter is a class, not an interface. Commented Dec 27, 2022 at 11:18
  • @kellermat actually VS CODE says it is interface when you hover mouse over it and it shows the tooltip: (alias) interface EventEmitter<T> import EventEmitter Commented Dec 27, 2022 at 11:21
  • I know what you mean and I'm a bit confused now too. My source was actually: angular.io/api/core/EventEmitter Commented Dec 27, 2022 at 11:29
  • When you write new XXX you are referring to a value named XXX, not a type named XXX, even though there might be such a type in scope. So EventEmitter is presumably the name both of a constructor value and of an interface type. This sort of thing happens automatically with class declarations (class Foo {} creates both a value named Foo and a type named Foo) but it can be done separately (like interface Bar {} and then declare const Bar: new () => Bar). Commented Dec 27, 2022 at 16:30
  • Could you edit to provide a minimal reproducible example that demonstrates your issue in a standalone IDE, including any import statements? If you can do that that I could pinpoint exactly where the two definitions of EventEmitter are and why things behave as they behave. Right now, though, all I have is that EventEmitter is not defined. If you make such an edit and want me to take another look, please mention @jcalz in a comment to notify me. Commented Dec 27, 2022 at 16:32

1 Answer 1

1

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.