The Event Delegation Pattern

Alex Reardon
InstructorAlex Reardon

Share this video with your friends

Send Tweet
Published a year ago
Updated a year ago

Event delegation is a simple, but powerful leveraging of the DOM event system which allows for easier adding of functionality to dynamically created content; as well as being an interesting performance optimization.

Instructor: [0:00] Here I'm adding a click event listener to the button. Inside of my onclick function, I'm going to call this addButton() function. What this addButton function is going to do is it's going to create a new button element. It's going to set the text of the button to be "Created button" and then this incremented count variable that I've stored up here.

[0:22] It's going to add the new button to the child element. The child element is the immediate parent of our button element. I'm getting access to that child element by using the querySelector API and passing in the class name of that element. When I click the button, I see that I'm able to add more and more buttons to the page.

[0:42] I'd like to change this application so that when I click on any of these buttons, they would also add new buttons. If I click on them now, they don't do anything.

[0:53] The first thing I can do is add a new click event listener to all of these created buttons, which themselves will call addButton when they're clicked. Now I can click on these created buttons and they will also create new buttons.

[1:08] This approach works fine. I'm creating new click event listeners for each new button. A drawback to this approach is that when I was dynamically adding content, I needed to manually add the event listeners. Even though I'm not doing it here, at some point I will need to unwire these events too.

[1:24] An alternative pattern is called event delegation, which sounds scary but is actually just a really clever leveraging of the DOM event system.

[1:34] We know that events have three phases. The first they go through a capture phase where they work down through a tree of event targets towards the event target. Then they have a target phase where they go through the event target. Then they have a bubble phase where they go back up to the root event target. The event delegation pattern leverages this event flow.

[1:55] Now, rather than adding a click event listener to the button, I'm going to add it to the parent of the button, which is the child element. Whenever a click event hits the child element, it's going to add a button.

[2:11] If I come over to my example, when I click the button, I'll see that I'm able to create new buttons. Now, if I click on one of these created buttons, I'm also able to now create new buttons, but we're not done yet.

[2:23] Firstly, I can click anywhere in this child element to create new buttons. That's probably not what we want. I've added a guard here and the purpose of the guard is to establish that the target of the event is a button. I'm checking, is the event target an instance of element and is the event target's tag name button?

[2:43] Just a reminder, the target of the event will be where the event is heading to or the source of the event which when we click on a button will be the button. When I click on the original button, I'll see I'm now adding new buttons, and I can click on these buttons to add more buttons, but if I click somewhere else in the child element, I'm not adding a button.

[3:05] These guards are a bit of a tripping point for this pattern. Say if I come over to my index.html and I wrap the content of my button inside of a span. When I click on the button, the tag name of the target will be span and not button, because the target of the event is the inner span and not the button outside it.

[3:28] To get around this problem, I'll use the .closest() API. What this is going to do is check if the event target is itself a button or if it's sitting inside of a button. Now I can click on the span inside of the button and I'll create a new button because .closest found the parent button on the event target which was the span and so this guard was satisfied.

[3:57] These created buttons don't have an inner span, but I can click on them and create new buttons because the .closest function starts its search on the current element. If the selector matches the current element, then this will also be satisfied.

[4:14] A few other things to note while we're here. I could have added this event listener to any parent of the button. I could add it to say the window and our behavior would still be the same.

[4:25] Keep in mind the higher up the DOM tree that you add this event listener, the more robust this guard will need to be to ensure that you're matching what you expect to match. Also, for now, this click event listener is using the default capture value, which is false.

[4:40] This is a bubble event listener. It'll get picked up in the bubble phase. If you we're using the event delegation pattern on an event that does not bubble, then you'll need to add your event listener in the capture phase.

[4:52] We've seen how the event delegation pattern has helped us with dynamically created content. The event delegation pattern is also a really interesting performance optimization.

[5:04] Because [inaudible] to add a new event listener to every single one of our new buttons, we can have a single event listener for all of our buttons. Say if we were to add hundreds and hundreds of buttons with the event delegation pattern, all of these buttons can use the same event listener.

[5:23] To visually demonstrate what we just did, what I was originally doing was adding an event listener to every single new target that was created. That's fine, it's going to pick up the event, no worries.

[5:33] The event delegation pattern is adding an event listener higher in your event listener tree and leveraging the fact that this event listener higher in the tree will get access to where the event was traveling to, the event target.

~ 3 months ago

At 3.00 why check " instanceof Element && === 'BUTTON'" and not only the second condition ?

~ 3 months ago

At 3'43 the same question for " instanceof Element &&'button')" -)

Alex Reardon
Alex Reardoninstructor
~ 3 months ago

Hi antonin,

This is me just being extremely safe with my types.

The property is of type EventTarget, and not Element. So I am doing the instanceof Element check so that I can safely use the Element.tagName property and Element.closest function. The property could be anything that implements the EventTarget interface.

The code in my example would still run totally fine without the instanceof checks. If you are working with TypeScript then you will need to narrow the EventTarget type down to Element before it will let you use any Element functionality