Custom component events are an essential feature in any modern web framework.
Within Angular, component events are created by combining the @Output decorator with EventEmitter.
This guide explains how to leverage @Output for emitting custom component events, and it also addresses a frequent source of confusion that often arises during event design.
Table of Contents
The following subjects are covered in this post:
- What is
@Output? - Basic Syntax of
@Output - What data type can @Output emit?
- Parent-Child data communication using
@Output - Accessing emitted data in the template
- Is it a must to pass the $event to the handler method?
- Must the name be
$eventin the template? @Outputwith aliases- How to set outputs in the
@Componentdecorator - How to set output alias in the
@Componentdecorator - What type of data type can you bind to @Output?
- Can the @Output be used to broadcast events to multiple components?
- How often can I use @Output?
- How can I specify the type of data emitted by an @Output?
- Must @Output be used with EventEmitter?
- Do the @Ouput and @Input have to be in the same component?
- Do the custom events bubble up to the DOM?
- What happens to the outputs when a component is extended?
- Are output names case-sensitive?
- Common mistakes when using
@Output - Summary
Understanding @Output
The @Output decorator designates a component property as an output, serving as a channel for the component to communicate events upward to its parent components.
An @Output() property is always paired with an EventEmitter.
When the EventEmitter fires, any parent component with an event binding for that event can react.
Core Syntax of @Output
Here’s the fundamental syntax for @Output:
@Output({
alias?: string;
}) propertyName = new EventEmitter<type>();
alias: The name of the DOM property the output is bound to.
propertyName: The class field decorated with @Output that emits events. If omitted, it defaults to the property name.
type: A generic parameter specifying the event payload's data type. Event consumers will receive an instance of this type.
An illustration of @Output() in practice:
@Component({
selector: "app-child",
template: ` <button (click)="emitEvent()">
Send Data to Parent
</button> `,
})
export class ChildComponent {
@Output()
myEvent = new EventEmitter<string>();
emitEvent() {
this.myEvent.emit("Hello World!");
}
}
In this example, ChildComponent defines a myEvent property marked with @Output.
With this setup, the emitEvent method can be invoked at any point to emit an event of type myEvent to the external world.
The parent component must subscribe to this output to respond appropriately.
The parent's template would look something like this:
@Component({
selector: "app-parent",
template: `
<app-child (myEvent)="handleEvent($event)" />`,
})
export class ParentComponent {
handleEvent(event: string) {
console.log(event);
}
}
The ParentComponent includes the app-child and uses the () event-binding syntax to listen for the myEvent event, assigning the handleEvent method as the handler.
This handler is invoked whenever myEvent.emit() is called within the child.
The handleEvent method then processes the data emitted by myEvent.
Pay attention to the $event special variable, which holds the data passed to the emit() function.
This mechanism facilitates decoupled, event-driven communication from the child to the parent component.
Using Aliases with @Output
By default, the @Output() property’s name doubles as the event name. However, a different name can be assigned when needed.
This is achieved by providing an alias to the @Output decorator:
@Output('customEvent')
myEvent = new EventEmitter<string>();
The name customEvent will then be used for event binding with the child component:
@Component({
selector: "app-parent",
template: `
<app-child (customEvent)="handleEvent($event)" />`,
})
export class ParentComponent {
handleEvent(event: string) {
console.log(event);
}
}
Using the previous (myEvent) binding will result in an error.
Steering Clear of a Common @Output Pitfall
This frequent mistake can lead to code that’s difficult to maintain.
The purpose of @Output is to report custom events; it’s not intended for explicitly triggering actions in other components.
Though the distinction might seem subtle initially, understanding it is critical.
These are two separate concepts that shouldn't be conflated:
- Event: Reports an internal state change within the component
- Command: Explicitly instructs another component to perform a specific action
The difference may be subtle, but it’s fundamental.
When issuing a command, you often have extensive knowledge about the receiver and its intended action.
This isn't the case with events.
Consider an example that illustrates this issue and its potential problems:
@Component({
selector: "child",
template: ` <button (click)="onClick()">
Trigger logic
</button> `,
})
export class ChildComponent {
@Output()
updateTotalCoursesCounter = new EventEmitter<>();
onClick() {
if (someBusinessLogic) {
this.updateTotalCoursesCounter.emit();
}
}
}
Notice the output's name: it implies a command rather than an event.
This output instructs an external component to execute a specific action.
This design is problematic because the child component holds too much information about the internal operations of an unrelated component.
It's likely that the business logic is in the wrong layer of the application, not within the child component itself.
Now, let’s convert this into a more appropriate event output:
@Component({
selector: "child",
template: ` <button (click)="onClick()">
Trigger logic
</button> `,
})
export class ChildComponent {
@Output()
somethingHappened = new EventEmitter<>();
onClick() {
this.somethingHappened.emit();
}
}
Here, we're simply notifying the outside world that something has occurred, without prescribing a response.
We're only reporting an internal state change, leaving it to external consumers to decide on any reaction.
This way, the child component remains unaware of which components are interested in the event or what actions they might take.
The child component makes no assumptions about how, where, when, or if the custom event will be consumed.
The decision on how to react is left to the output's consumers:
@Component({
selector: "app-parent",
template: `
<app-child (somethingHappened)="handleEvent()" /> `,
})
export class ParentComponent {
handleEvent() {
if (someBusinessLogic) {
// update the total courses counter
}
}
}
In this case, the parent component has subscribed to the event and responds accordingly. The child, however, has no knowledge of which parent handled the event or the nature of the response.
Notice that the business logic is now correctly placed outside the child component.
Finding this logic within the child component would have been unexpected and incorrect.
Key Takeaway: Avoiding the Event vs. Command Design Trap
When designing outputs, always remember: an event is not a command. @Output is for reporting events, not for explicitly triggering actions in other components.
A practical method to ensure adherence to this rule is to inspect the output's name carefully.
If the name isn't a past-tense verb, there's a risk the output is being misused to trigger an action that the component shouldn't know about.
Past-tense verbs are strong indicators that an output is reporting a state change rather than issuing a directive.
Let's now address some common questions about the use of Output.
Frequently Asked Questions about @Output and EventEmitter
Below are answers to some of the most common questions regarding @Output and EventEmitter.
What data types can @Output emit?
The @Output EventEmitter can emit data of any type.
This includes primitives, objects, or arrays:
// primitive values.
this.myEvent.emit("Hello World!");
this.myEvent.emit(90);
this.myEvent.emit(true);
// You can emit custom event objects
this.myEvent.emit({
name: "Chidume Nnamdi",
age: 90,
});
this.myEvent.emit([1, 2, 3, "4", true]);
const person = Person();
this.myEvent.emit(person);
Is passing the $event to the handler mandatory?
No, it's not required, regardless of the emitted data type.
@Component({
selector: "app-parent",
template: `
<app-child (myEvent)="handleEvent()" /> `,
})
export class ParentComponent {
handleEvent() {
console.log("Hello World!");
}
}
Can @Output broadcast events to multiple components?
@Output() is specifically for direct child-to-parent communication. It is neither possible nor intended for broadcasting events to multiple components.
Is EventEmitter mandatory with @Output?
Yes, an @Output property must always be an instance of an EventEmitter.
Do custom events bubble up to the DOM?
No, unlike most standard browser events, custom Angular events do not bubble up to the DOM.
Is manually bubbling custom events up the component tree advisable?
No, implementing this manually is an anti-pattern. For communication between components without a direct parent-child relationship, a shared service should be used.
What happens to outputs when a component is extended?
When you extend a component, the derived component inherits the outputs from its parent component.
Are output names case-sensitive?
Yes. The output property name must exactly match the class field name, as any discrepancy will cause an error.
Defining outputs within the @Component decorator
It's also possible to define output properties without the @Output decorator by using an alternative syntax within the @Component decorator.
This approach can be useful in certain scenarios:
@Component({
selector: "app-child",
template: ` <button (click)="emitEvent()">
Send Data to Parent
</button> `,
outputs: ["myEvent"],
})
export class ChildComponent {
myEvent = new EventEmitter<string>();
emitEvent() {
this.myEvent.emit("Hello World!");
}
}
Setting an output alias in the @Component decorator
An alias is set by separating the two names with a colon :. The left side is the class field name, and the right side is the alias:
@Component({
selector: "app-child",
template: ` <button (click)="emitEvent()">
Send Data to Parent
</button> `,
outputs: ["myEvent: customEvent"],
})
export class ChildComponent {
myEvent = new EventEmitter<string>();
emitEvent() {
this.myEvent.emit("Hello World!");
}
}
I hope this post was helpful. If you'd like to be alerted about future posts like this, please subscribe to our newsletter:
You'll also receive updates on the latest developments in the Angular ecosystem.
For a comprehensive exploration of Angular Core features, including @Output, check out the Angular Core Deep Dive Course:
Summary
This guide has provided a thorough examination of the @Output decorator.
We've covered its basic syntax, features, and, most importantly, clarified that its purpose is to report custom events reflecting internal state changes, not to command other components to act.
A helpful guideline is to ensure your output names use past-tense verbs, which often indicates that the output is being used correctly and maintainably.
Should you have any questions or comments, please feel free to reach out. I'm here to help!
