Angular event binding uses the (event) syntax to listen to either native DOM events (click, input, submit, keydown, etc.) or custom events emitted by child components via @Output(). When the event fires, Angular runs the bound template statement/method with access to $event, and (in typical setups) triggers change detection for affected views. Events trigger change detection, so test performance under rapid input and large templates.
How does Angular event binding work (DOM events vs @Output events), and what are the common pitfalls?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea(event)="..." attaches a listener from the template to a template statement (usually a component method). It’s one-way: view ➜ component. The event can be a native DOM event or a custom event emitted by a child component (@Output()).
Event source | Template syntax | What $event is | Typical use |
|---|---|---|---|
Native DOM event |
| A DOM | User interactions on elements |
Child component @Output |
| Whatever the child | Child ➜ parent communication |
Basic syntax
Angular uses parentheses to bind an event name to a handler. Use $event to access the event payload.
<button type="button" (click)="inc()">+</button>
<input
type="text"
(input)="onInput(($event.target as HTMLInputElement).value)"
/>
<form (submit)="onSubmit($event)">
<button type="submit">Save</button>
</form>
<!-- key filtering -->
<input (keyup.enter)="save()" (keydown.escape)="cancel()" />
import { Component } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './demo.component.html'
})
export class DemoComponent {
count = 0;
inc(): void {
this.count += 1;
}
onInput(value: string): void {
// avoid event: any; pass the extracted value
console.log('value:', value);
}
onSubmit(e: SubmitEvent): void {
e.preventDefault();
// submit logic
}
save(): void {}
cancel(): void {}
}
Custom events with @Output()
Child emits; parent listens with the same (event) syntax. The payload type is whatever you emit (and should be typed).
import { Component, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'app-child',
template: `<button type="button" (click)="save()">Save</button>`
})
export class ChildComponent {
@Output() saved = new EventEmitter<{ id: string }>();
save(): void {
this.saved.emit({ id: 'u1' });
}
}
// parent.html
// <app-child (saved)="onSaved($event)"></app-child>
// parent.ts
// onSaved(payload: { id: string }) { ... }
Interview-grade pitfalls | What goes wrong | Fix |
|---|---|---|
Using | You lose type safety and misuse | Use concrete types (e.g. |
Heavy logic in template statements | Hard to test/read; can run often and become brittle | Keep templates thin; call a method and do logic in TS |
Forgetting default browser behavior | Forms reload page; links navigate unexpectedly | Use |
Event propagation surprises | Nested clicks trigger parent handlers unexpectedly | Use |
OnPush expectations | UI doesn’t update when state changes outside Angular awareness | Events in templates normally trigger checks; for external sources use signals/async pipe or |
Using EventEmitter outside @Output | Misused as a general event bus; awkward lifecycle and ownership | Use RxJS Subjects/services for cross-component events; keep EventEmitter for outputs |
Practical scenario
A parent listens to a child component's @Output and a native button click in the same template.
Common pitfalls
- Forgetting to pass or use
$event. - Misnaming outputs and silently missing events.
- Expensive handlers causing performance issues under rapid input.
Events trigger change detection. Test with rapid clicks and consider OnPush for performance.
(event) binds template events to component logic. For DOM events, $event is a DOM event object; for @Output(), $event is the emitted payload. Keep handlers typed, keep templates thin, and be deliberate about default behaviors and propagation.