Checkout Radzen Demos and download latest Radzen!
Angular components and HTML elements communicate with the outside world through events. The Angular event binding syntax is the same for components and elements - the target event within parentheses, equal sign and a template statement.
<button (click)="onClick()">Click</button>
HTML Elements
Some of the events, that HTML elements fire, propagate through the DOM hierarchy. This propagation process is called event bubbling. Events are first handled by the innermost element and then propagate to the outer elements until they reach the root.
DOM event bubbling works seamlessly with Angular (plunkr).
@Component({
selector: 'event-bubbling',
template: `
<div (click)="onClick()">
<button>Click</button>
</div>
`
})
export class EventBubblingComponent {
onClick() {
alert('Click');
}
}
Clicking the button displays the message even though the parent element handles the event.
Angular Components
Angular provides support for custom events via Output
properties and the EventEmitter
. Unlike DOM events Angular
custom events do not bubble. Trying to bind to a custom event of a child component will lead to unknown directive
error.
Binding to a DOM event triggered by a child element works as expected (plunkr).
@Component({
selector: 'event-bubbling',
template: `
<div>
<button>Click</button>
</div>
`
})
export class EventBubblingComponent {
}
@Component({
selector: 'my-app',
template: `
<div>
<event-bubbling (click)="onClick()"></event-bubbling>
</div>
`,
})
export class App {
onClick() {
alert('Click!');
}
}
Custom Events Named After DOM Events
What if EventBubblingComponent
emitted a custom event called click
? Let’s try that (plunkr).
@Component({
selector: 'event-bubbling',
template: `
<div>
<button (click)="onClick('Button 1')">Button 1</button>
<button (click)="onClick('Button 2')">Button 2</button>
</div>
`
})
export class EventBubblingComponent {
@Output() click = new EventEmitter();
onClick(button: string) {
this.click.next(button);
}
}
@Component({
selector: 'my-app',
template: `
<div>
<event-bubbling (click)="onClick($event)"></event-bubbling>
</div>
`,
})
export class App {
onClick(button: string) {
alert(button);
}
}
Clicking a button displays two messages now - first the button title (as expected) and then [Object object]
(or [Object MouseEvent]
).
Where does the second alert come from? In this case Angular handles both the custom and DOM click events.
The DOM event handler executes second and receives a DOM Event object as a parameter hence alert
shows [Object object]
.
How to fix that? DOM events provide a mechanism that can prevent bubbling. It is the stopPropagation
method. Here is how to use it (plunkr).
@Component({
selector: 'event-bubbling',
template: `
<div>
<button (click)="onClick($event, 'Button 1')">Button 1</button>
<button (click)="onClick($event, 'Button 2')">Button 2</button>
</div>
`
})
export class EventBubblingComponent {
@Output() click = new EventEmitter();
onClick(event: Event, button: string) {
event.stopPropagation();
this.click.next(button);
}
}
The problem is now fixed. Clicking a button displays only one message.
Takeaway
Event bubbling allows a single handler on a parent element to listen to events fired by any of its children.
Angular supports bubbling of DOM events and does not support bubbling of custom events.
Mixing the names of custom and DOM events (click, change, select, submit) could be problematic. Use stopPropagation
to avoid
double event handler invocation. Prefer naming custom events after their intent and not cause.
<!-- custom event clashes with DOM event -->
<product-form (submit)="onSubmit($event)"></product-form>
<!-- custom event shows its intent and does not clash -->
<product-form (save)="onSaveProduct($event)"></product-form>