Event Binding in Angular
Event binding lets your component respond to user actions — clicks, key presses, mouse movements, form submissions, and more. The parentheses syntax (eventName)="handler()" means "when this event fires, call this handler in the component class."
Event binding is a one-way flow: data travels from the template to the component class.
Basic Syntax
import { Component } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Count: {{ count }}</p>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset</button>
`,
})
export class CounterComponent {
count = 0;
increment() { this.count++; }
decrement() { this.count--; }
reset() { this.count = 0; }
}
The $event Object
Angular exposes the native DOM event object as $event. Pass it to your handler when you need event details like the key pressed, mouse coordinates, or the target element's value.
@Component({
selector: 'app-event-demo',
standalone: true,
template: `
<!-- Pass $event for full event details -->
<input (input)="onInput($event)" placeholder="Type here" />
<p>You typed: {{ typedText }}</p>
<!-- Mouse position -->
<div class="canvas" (mousemove)="onMouseMove($event)">
Move mouse here
</div>
<p>Mouse: {{ mouseX }}, {{ mouseY }}</p>
<!-- Form submit -->
<form (submit)="onSubmit($event)">
<button type="submit">Submit</button>
</form>
`,
})
export class EventDemoComponent {
typedText = '';
mouseX = 0;
mouseY = 0;
onInput(event: Event) {
this.typedText = (event.target as HTMLInputElement).value;
}
onMouseMove(event: MouseEvent) {
this.mouseX = event.clientX;
this.mouseY = event.clientY;
}
onSubmit(event: SubmitEvent) {
event.preventDefault();
console.log('Form submitted');
}
}
Inline Statements
For simple cases, you can write a short statement directly in the template without defining a method. This is fine for one-liners but prefer named methods for anything involving logic.
<!-- Inline assignment --> <button (click)="isVisible = !isVisible">Toggle</button> <!-- Inline method with argument --> <button (click)="removeItem(item.id)">Delete</button> <!-- Multiple statements (not recommended — hard to read) --> <button (click)="count = count + 1; lastClicked = 'increment'">+</button> <!-- Accessing $event inline --> <input (keyup)="lastKey = $event.key" />
Keyboard Event Filtering
Angular supports key event filtering directly in the binding syntax using dot notation. This saves you from writing if (event.key === 'Enter') inside your handler.
@Component({
selector: 'app-search',
standalone: true,
template: `
<!-- Only fires on Enter key -->
<input (keyup.enter)="search()" [(ngModel)]="query" />
<!-- Specific key combinations -->
<textarea (keydown.control.s)="save($event)" placeholder="Edit content"></textarea>
<input (keydown.escape)="clearInput()" [(ngModel)]="text" />
<!-- Arrow keys -->
<div (keydown.arrowUp)="moveUp()" (keydown.arrowDown)="moveDown()" tabindex="0">
Navigate with arrow keys
</div>
`,
})
export class SearchComponent {
query = '';
text = '';
search() {
console.log('Searching for:', this.query);
}
save(event: KeyboardEvent) {
event.preventDefault();
console.log('Saved!');
}
clearInput() { this.text = ''; }
moveUp() { console.log('Moving up'); }
moveDown() { console.log('Moving down'); }
}
Common DOM Events
Event | Binding | When It Fires |
|---|---|---|
click | (click) | Mouse button pressed and released |
dblclick | (dblclick) | Double click |
mouseover | (mouseover) | Mouse enters element |
mouseout | (mouseout) | Mouse leaves element |
mousemove | (mousemove) | Mouse moves over element |
keydown | (keydown) | Key pressed down |
keyup | (keyup) | Key released |
input | (input) | Input value changed (every keystroke) |
change | (change) | Input value committed (on blur) |
focus | (focus) | Element receives focus |
blur | (blur) | Element loses focus |
submit | (submit) | Form submitted |
scroll | (scroll) | Element scrolled |
Component Event Binding with @Output
Child components emit custom events via @Output() and EventEmitter. The parent binds to these custom events with the same parentheses syntax used for DOM events.
// child: like-button.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-like-button',
standalone: true,
template: `
<button (click)="toggle()">
{{ liked ? 'Unlike' : 'Like' }} ({{ likeCount }})
</button>
`,
})
export class LikeButtonComponent {
@Output() likeChanged = new EventEmitter<{ liked: boolean; count: number }>();
liked = false;
likeCount = 42;
toggle() {
this.liked = !this.liked;
this.likeCount += this.liked ? 1 : -1;
this.likeChanged.emit({ liked: this.liked, count: this.likeCount });
}
}
// parent component
@Component({
selector: 'app-post',
standalone: true,
imports: [LikeButtonComponent],
template: `
<app-like-button (likeChanged)="onLikeChanged($event)" />
<p>Status: {{ likeStatus }}</p>
`,
})
export class PostComponent {
likeStatus = '';
onLikeChanged(data: { liked: boolean; count: number }) {
this.likeStatus = data.liked
? `Liked! Total: ${data.count}`
: `Unliked. Total: ${data.count}`;
}
}
Stopping Event Propagation
Use $event.stopPropagation() to prevent an event from bubbling up to parent elements, and $event.preventDefault() to block the browser's default behaviour.
<!-- Stop click from bubbling to parent overlay -->
<div class="overlay" (click)="closeModal()">
<div class="modal" (click)="$event.stopPropagation()">
Modal content — clicking here won't close the overlay
</div>
</div>
<!-- Prevent default link navigation -->
<a href="/old-path" (click)="navigate($event)">Custom navigation</a>
<!-- Prevent form default submission -->
<form (submit)="handleSubmit($event)">
<button type="submit">Submit</button>
</form>
Event Binding in Loops
Inside @for or *ngFor, pass the item as an argument to the handler to identify which item was acted upon.
@Component({
selector: 'app-todo-list',
standalone: true,
template: `
<ul>
@for (item of items; track item.id) {
<li>
<span [class.done]="item.done">{{ item.text }}</span>
<button (click)="toggle(item)">Toggle</button>
<button (click)="remove(item.id)">Delete</button>
</li>
}
</ul>
<button (click)="addItem()">Add Item</button>
`,
})
export class TodoListComponent {
items = [
{ id: 1, text: 'Learn Angular', done: false },
{ id: 2, text: 'Build an app', done: false },
];
toggle(item: { id: number; text: string; done: boolean }) {
item.done = !item.done;
}
remove(id: number) {
this.items = this.items.filter(i => i.id !== id);
}
addItem() {
const id = Date.now();
this.items.push({ id, text: `New item ${id}`, done: false });
}
}
Summary
(eventName)="handler()" — calls a method when a DOM or custom event fires.
$event is the native event object — pass it when you need event details.
Inline statements like (click)="count++" are fine for trivial cases.
Angular key filtering: (keyup.enter), (keydown.control.s), (keydown.escape).
Child components emit custom events via @Output + EventEmitter; parent binds with (customEvent).
Use $event.stopPropagation() and $event.preventDefault() for fine-grained control.
In loops, pass the item as a method argument to identify which item was acted upon.