Custom Event Binding (& @Output)
Send data from child to parent with @Output and EventEmitter — custom events for component communication.
Custom Event Binding (& @Output)
@Output() lets a child component emit events that the parent can listen to. This is how data flows upward — from child to parent.
Creating a Custom Event
// child: todo-item.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-todo-item',
template: `
<div class="todo-item">
<span>{{ title }}</span>
<button (click)="onDelete()">Delete</button>
</div>
`
})
export class TodoItemComponent {
@Input() title: string = '';
@Output() delete = new EventEmitter<string>();
onDelete(): void {
this.delete.emit(this.title); // Emit the event with data
}
}
Listening in the Parent
// parent: app.component.ts
export class AppComponent {
todos = ['Learn Angular', 'Build an app', 'Deploy'];
onTodoDeleted(title: string): void {
this.todos = this.todos.filter(t => t !== title);
console.log(`Deleted: ${title}`);
}
}
// parent template
<app-todo-item
*ngFor="let todo of todos"
[title]="todo"
(delete)="onTodoDeleted($event)">
</app-todo-item>
The parent uses (delete)="handler($event)" — the same event binding syntax as native DOM events. $event contains the data passed to emit().
Emitting Complex Data
// child
interface TodoEvent {
id: number;
action: string;
}
@Output() todoAction = new EventEmitter<TodoEvent>();
onComplete(): void {
this.todoAction.emit({ id: this.id, action: 'complete' });
}
onEdit(): void {
this.todoAction.emit({ id: this.id, action: 'edit' });
}
// parent template
<app-todo-item
(todoAction)="handleAction($event)">
</app-todo-item>
// parent
handleAction(event: TodoEvent): void {
if (event.action === 'complete') {
this.markComplete(event.id);
} else if (event.action === 'edit') {
this.openEditor(event.id);
}
}
@Input + @Output Together
// A reusable counter component:
@Component({
selector: 'app-counter',
template: `
<div class="counter">
<button (click)="decrement()">−</button>
<span>{{ count }}</span>
<button (click)="increment()">+</button>
</div>
`
})
export class CounterComponent {
@Input() count: number = 0;
@Output() countChange = new EventEmitter<number>();
increment(): void {
this.count++;
this.countChange.emit(this.count);
}
decrement(): void {
this.count--;
this.countChange.emit(this.count);
}
}
// Parent — two-way binding shorthand:
<app-counter [(count)]="itemCount"></app-counter>
<p>Items: {{ itemCount }}</p>
When @Output is named propertyChange (matching the @Input name + "Change"), you can use [()] two-way binding syntax!
Complete Data Flow
Parent Component
│ ▲
│ @Input [data] │ @Output (event)
▼ │
Child Component ─── emit() ───┘
Key Takeaways
@Output()+EventEmitterlets children send data to parents- Parent listens with
(eventName)="handler($event)" emit(data)sends data —$eventreceives it in the parent- Name an Output
propChangeto enable[(prop)]two-way shorthand - @Input = data flows down, @Output = events flow up