Let’s say that you have a list of elements and you want to listen for an event on each one of them. One way of doing it would be to loop over all the elements in the list and add event listeners on them individually. But as you can see this could be an inefficient approach and this is where the concept of event delegation comes into play.
Event delegation is a concept where we listen for the event on the parent element and then on the basis of the event object that is passed in the event handler we are able to access the child items. This is useful when we have more child elements and if the content in the child elements is dynamically added.
General Syntax
parentElement.addEventListener( eventType, eventHandler, useCapture)
Before jumping into event delegation let’s first understand the concept of event propagation
Event Propagation
Capturing phase
When an event takes place on a target element, the event handlers on the parent elements are executed first and after that, the event is propagated down towards the target element and the target event handler is executed.
Execution steps: Document→ Element 1→Element 2→Element 3 → …→Target element
Target phase
It is a phase where the event reaches the target element and the event handler is executed.
Bubbling phase
When an event takes place on a target element, the event handler on the target element is executed first and after that, the event is propagated up to its parent elements and their event handlers are executed.
Execution steps: Target element → …→Element 3→Element 2→Element 1→Document
Note: One can also stop the propagation if they want by using event.stopPropagation() inside of the handler*
Let’s get a better understanding with an example
Html
<div class="parent-box">
<div class="child-box" data-number=1>
<div class="child-box-1" data-number=2>
<div class="child-box-2" data-number=3>
</div>
</div>
</div>
</div>
CSS
{
margin:0;
padding:0;
box-sizing:border-box;
}
.parent-box{
height:100vh;
background-color:red;
}
.parent-box,.child-box,.child-box-1,.child-box-2{
display:flex;
justify-content:center;
align-items:center;
}
.child-box{
height:200px;
width:200px;
background-color:blue;
}
.child-box-1{
height:130px;
width:130px;
background-color:green;
}
.child-box-2{
height:50px;
width:50px;
background-color:yellow;
}
With useCapture set to false
JS
const parentEl=document.querySelector(".parent-box");
const childBox=document.querySelector(".child-box");
const childBoxOne=document.querySelector(".child-box-1");
const childBoxTwo=document.querySelector(".child-box-2");
childBox.addEventListener("click",(e)=>{
console.log("child box")
});
childBoxOne.addEventListener("click",(e)=>{
console.log("child box 1")
});
childBoxTwo.addEventListener("click",(e)=>{
console.log("child box 2")
});
The JS code has the useCapture equal to false so when we click on the .child-box-2 the event propagates from the .child-box-2 → .child-box-1 → .child-box
Console output:
With useCapture set to true
JS
const parentEl=document.querySelector(".parent-box");
const childBox=document.querySelector(".child-box");
const childBoxOne=document.querySelector(".child-box-1");
const childBoxTwo=document.querySelector(".child-box-2");
childBox.addEventListener("click",(e)=>{
console.log("child box")
},true);
childBoxOne.addEventListener("click",(e)=>{
console.log("child box 1")
},true);
childBoxTwo.addEventListener("click",(e)=>{
console.log("child box 2")
},true);
Console output:
Event delegation
Html
<div class="parent-box">
<div class="child-box" data-number=1>
</div>
<div class="child-box" data-number=2>
</div>
<div class="child-box" data-number=3>
</div>
<div class="child-box" data-number=4>
</div>
<div class="child-box" data-number=5>
</div>
<div class="child-box" data-number=6>
</div>
</div>
CSS
{
margin:0;
padding:0;
box-sizing:border-box;
}
.parent-box{
height:100%;
width:100%;
background-color:red;
display:flex;
justify-content:center;
align-items:center;
gap:2rem;
flex-wrap:wrap;
padding:2rem;
}
.child-box{
height:200px;
width:200px;
background-color:blue;
}
JS
const parentEl=document.querySelector(".parent-box");
parentEl.addEventListener("click",(e)=>{
if(e.target.classList.contains('child-box')){
console.log(`Box No: ${e.target.dataset.number}`);
}
});
Here we are basically applying the “click” event on the “.parent-box” and then console logging the box number. As you can see in the above example that by accessing the event.target we can get the child elements.
Few points to remember
A lot of the time the element we want to target also has child elements due to which the event.target gives us the child element of the target element. In that case, we should use closest( selectors ) property on the target element
event.target(child) gives the element on which the event takes place and event.currentTarget (parent) gives the element on which the event listener is attached
🙌 So this was about event delegation. Hope you were able to understand the concept of event delegation. You can connect with me on Twitter and LinkedIn.