Event Delegation with RxJS

Shane Osbourne
InstructorShane Osbourne
Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 5 years ago

Event delegation is an extremely powerful technique. It allows you to use a single event handler to listen to events on any number of child elements. It also has the added benefit of working with dynamically added elements without even a single line of additional code. Libraries such as jQuery offer this feature right out of the box, but with RxJS and the fromEvent helper, we’ll need to utilise the selector function to achieve the same result. NOTE: The element.closest method shown in this video required a polyfill for older version of IE which can be found here https://github.com/jonathantneal/closest

[00:00] Event delegation is an excellent technique to learn for two main reasons. First of all, from a performance point of view, you always want to be limiting the amount of event listeners that your application adds to DOM elements. So in this example, if you wanted to listen for clicks on each of these buttons we don't need to add five event listeners, instead we can add one to the wrapper container and then within that single handler we can determine if it was a button that was clicked.

[00:28] So performance is a great reason to use event delegation, but there's a second reason that's equally as useful. That's the ability to act on elements that are dynamically added. So if we were to add another button to this list, write to the console, you can see the click event still happens on this button, even though we've not added a click event for it. Libraries such as jQuery offer this functionality right out of the box.

[00:59] Here you can see we're selecting the wrapper, here, adding a click event, and then providing a selector for the elements that we're actually interested in. This means this handler function here will only ever be called if we click on a button.

[01:19] So clicking here does nothing, but clicking here gives us back the button element. So as you can see, event delegation is an extremely useful technique. Now let's look how we'll do this with Rx JS.

[01:32] Rx ships with a help method from event. From event, create an observable sequence by adding event listeners to any DOM elements you provide as the first argument. So if we call fromEvent with the result of a query select all, we'll select our wrapper and then any button inside it.

[01:57] The second parameter is the event name. We want clicks, and then we can subscribe to this sequence and log the result to the console. Now if we click a button, you see we get this mouse event.

[02:10] If we log the target instead, you can see we get the button as before. This appears to be working the same way as the jQuery example. But that's actually not the case.

[02:23] Query select all returns a collection of DOM nodes, in this case it will be all five buttons. From event will then loop over each item in this collection and add a separate event listener for it. We can prove this by diving into the source code for a moment. We search for fromEvent, we actually end up here with an event observable.

[02:51] The actual listeners are added here, and if we look we have a simple check to see if it was a node list or an HTML collection, for which case it loops over each one and creates an event listener. In which case we end up here, and you can see we're using the native add event listener from the browser.

[03:09] If we were to console log here, adding event listener, then reload, you can see this is called five times. So we've got five separate events for this.

[03:20] This is simply not good enough, so we're going to have to do a little bit more work to make this as efficient as the jQuery version. First of all, we'll switch to simply query selector, and provide just a wrapper. This means we get a single event on the wrapper, as you can see here.

[03:37] Then fromEvent, actually accepts a third argument which is a selector function. This function allows you to intercept every click event, and then whatever you return from this function is the value that will make it into the observable sequence.

[03:53] For example if I return the string "chain," it changes this to simply x. Every time I click on this wrapper now, I'm going to see the string "chain" here. So we can use this feature to do the event delegation.

[04:08] Because the selector function receives the browser event, we can return event.target.closest, and then provide the selector for our buttons. Closest is a method that's available on DOM element. If we save and run this again, now you see we get the button, but clicks on the wrapper provide a null value here.

[04:36] So we're close, but this is still not as good as the jQuery version. We could filter out those null values, save, now we don't get any events when we click here, but we get click events on the buttons. So, that's event delegation with Rx JS.

[04:56] We use the fromEvent helper, pass along a single DOM element, the string click will add a native click event to this element, we intercept each one of those events, and we return the closest element based on the selector provided here.

[05:12] We filter out null values as clicking on the wrapper itself will cause this to return null, and we never care about these null values. In our subscription, we then have access to only the button. To close out this lesson, let's look at how this block of code might look in a real-life application.

[05:30] It's more likely that you would have a function called something like delegate. You'd take in three parameters. You'd have the wrapper selector, the element selector, and the event name.

[05:44] We can copy the bulk of this code. We'll return here and swap out these strings for the arguments. We have wrapper selector, event name, and element selector. So now we have a function that we can call at any point. We will pass wrapper, button, and click. We can then subscribe and everything just works.

Dmitry
Dmitry
~ 8 years ago

Good example, but why just not filtering the event itself? like:

const wrapperEl = document.getElementById('wrapper');
const click$ = Rx.Observable.fromEvent(wrapperEl, 'click');

click$.filter((ev) => {
    return ev.target instanceof HTMLButtonElement;
  })

"closest" function will give you more logic than filtering exact target element.

Michael Russell
Michael Russell
~ 8 years ago

Looks like the jsbin embedded needs a pro account to be viewable :/

Markdown supported.
Become a member to join the discussionEnroll Today