Output Events
While inputs let data flow into a component, outputs let a child component notify its parent that something happened — a button was clicked, a value changed, a row was selected. Angular models this with the @Output() decorator paired with an EventEmitter, giving you a clean, typed, one-way channel that flows from child to parent. This keeps components decoupled: the child announces what happened without knowing or caring who is listening.
Declaring an output
An output is a public class property decorated with @Output() and assigned a new EventEmitter. The generic type parameter declares the shape of the payload the event carries. The child calls .emit(value) to fire the event, and the parent subscribes to it in the template using the familiar (eventName) event-binding syntax.
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<button (click)="increment()">Clicked {{ count }} times</button>
`,
})
export class CounterComponent {
count = 0;
@Output() countChange = new EventEmitter<number>();
increment(): void {
this.count++;
this.countChange.emit(this.count);
}
}
The parent listens for countChange and reacts to the emitted number:
import { Component } from '@angular/core';
import { CounterComponent } from './counter.component';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [CounterComponent],
template: `
<app-counter (countChange)="onCount($event)" />
<p>Last reported count: {{ latest }}</p>
`,
})
export class DashboardComponent {
latest = 0;
onCount(value: number): void {
this.latest = value;
}
}
The $event variable in the template holds whatever value the child passed to .emit(). Because the emitter is typed EventEmitter<number>, $event is strongly typed as number, and the compiler will flag a mismatch if onCount expects something else.
Tip:
EventEmitterextends RxJSSubject, but treat outputs purely as an event channel. Do not subscribe to them imperatively from a parent or rely on their observable nature — that is an implementation detail Angular reserves the right to change.
Emitting rich payloads
Outputs are not limited to primitives. A common pattern is emitting a typed object or a discriminated union describing the action that occurred. This is especially useful for list or table components where the parent needs context about which item triggered the event.
import { Component, Output, EventEmitter } from '@angular/core';
interface Product {
id: string;
name: string;
}
@Component({
selector: 'app-product-row',
standalone: true,
template: `
<div class="row">
<span>{{ product.name }}</span>
<button (click)="select.emit(product)">Buy</button>
<button (click)="remove.emit(product.id)">Remove</button>
</div>
`,
})
export class ProductRowComponent {
product: Product = { id: 'p1', name: 'Keyboard' };
@Output() select = new EventEmitter<Product>();
@Output() remove = new EventEmitter<string>();
}
You can call .emit() directly in the template, as shown above, or route through a method when there is logic to run first. Keep template-side .emit() calls to trivial pass-throughs.
The two-way binding convention
Angular has a special convention: if an input is named value and a matching output is named valueChange, consumers can use the banana-in-a-box [(value)] syntax for two-way binding. This is exactly how ngModel works under the hood.
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-toggle',
standalone: true,
template: `
<button (click)="toggle()">{{ checked ? 'On' : 'Off' }}</button>
`,
})
export class ToggleComponent {
@Input() checked = false;
@Output() checkedChange = new EventEmitter<boolean>();
toggle(): void {
this.checked = !this.checked;
this.checkedChange.emit(this.checked);
}
}
A parent can now write <app-toggle [(checked)]="isEnabled" />, which Angular desugars to [checked]="isEnabled" (checkedChange)="isEnabled = $event".
Renaming and aliasing outputs
The @Output() decorator accepts an optional alias — the public binding name used in templates — while the class property keeps its internal name. Use aliasing sparingly, since it adds an indirection that can confuse readers.
@Output('itemSelected') select = new EventEmitter<Product>();
Consumers bind to (itemSelected) even though the field is select. The general guidance is to keep the alias and the property name identical unless you have a strong reason.
How outputs differ from other communication
| Mechanism | Direction | Best for |
|---|---|---|
@Input() | Parent → child | Passing data down |
@Output() | Child → parent | Notifying of discrete events |
| Shared service | Any → any | Sibling / cross-tree state |
Signals (output()) | Child → parent | Modern signal-based components |
In Angular 17.3+ the function-based output() API supersedes the decorator for new code. It is fully covered on the signal inputs and outputs page; the @Output() decorator described here remains fully supported and is what you will see in the majority of existing codebases.
Best practices
- Name outputs as past-tense events (
saved,deleted,selectionChanged) rather than commands — the child reports what happened, the parent decides what to do. - Always declare the generic type on
EventEmitter<T>so payloads are strongly typed; avoidEventEmitter<any>. - Emit a meaningful payload, not a bare signal — give the parent the context it needs (the changed value, the affected entity, or its id).
- Follow the
xxx/xxxChangenaming pair only when you genuinely want two-way binding; otherwise pick a descriptive name. - Do not pass
EventEmitterinstances into a child or subscribe to outputs imperatively — they are a template-binding API, not a general event bus. - Prefer the newer
output()function for greenfield components to align with the signals direction of the framework.