Sessions is temporarily moving to YouTube, check out all our new videos here.

Deep Dive on Ember Events

Marie Chatfield speaking at EmberFest in October, 2017
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

Handling events like clicks and keypresses as users interact with an app is essential for any Ember application. Learn the fundamentals of how events work in Ember, starting with the basics of DOM events and working up to the lifecycle of Ember events. You'll be able to implement event listeners with confidence and debug with clarity once you have a complete mental model of Ember events.


My name is Marie Chatfield, and I'm here today to take you on a learning journey, all about Events and Ember. So, by way of quick introduction, I am a software engineer at Square, working in our San Francisco offices. And so one of the things Square does, is payment processing. So that's what small businesses use us to take credit cards, or other forms of payments from their merchant. And we have this gigantic Ember app, that has about 60 to 70 monthly contributors, with a bunch of different product teams, all building into this one giant app, that we call our dashboard, and that's where I spend the vast majority of my time. So, a couple of months ago, I was working on implementing a new feature with my team. And so we wanted to have this button, and then idea was you can click on the button, and it will open a menu, and then you can click it again, and then the menu goes away. So, it's really exciting stuff right. So, we decided to give our users, a little bit of extra information the first time they interacted with this new feature. So we decided to do a walk through tour. So, the idea is that, we will give them some context, as to what they were looking at, they could click on the button, it would automatically open the menu for them, and then show the next step of the tour. So, the way we that we began implementing this, was that we had this common tool tip component, that actually just had a little button, along with any information, and so they could click on that button, to advance the tour. And all that was nested inside of the actual button that we wanted our users to be clicking on. And so, I made one very small code change to this, and everything broke. So, let's go ahead and take a look at the responsible code change. That was it. So, let's see if we can figure out what broke, when I made that code change. So, this time users click on the button, it opens up the menu, as we expect, and immediately closes it again. At which point I was pretty confused, I had no idea what going on, or what caused it. So, I began debugging and investigating, and I could pretty quickly identify the exact comment that introduced the bug. But that's an awful whole other 72 hours, of reading source code, and opening up stactrices, and reading documentation, until I was finally able to figure out that, before I was exclusively listening for click events with Ember, and after I was listening for events with a combination of Ember and the native DOM. At which point you might be thinking, "Hang on, what's the difference between those two? "And what in the world has that tiny code change, "have anything to do with all of these words?" So, let's back it up all the way. So, the DOM or Document Object Model, is a way of describing how webpages work. So, browsers implement this, in order to go from text in a HTML file, to an actual living, breathing website that you can interact with. So, one of the really important concepts here, is the DOM tree, which is this hierarchical data structure, that defines everything on the page. And it all starts with the parent, which is the document. And everything else on a webpage, is one of the nodes in this tree. We also have a concept of events in the DOM, which is a way of saying that something has happened on the page. Usually because a user has done something like click on something or press a key, or submit a form. So, whenever all of that happens, DOM sends an an event to a particular node in the DOM tree, and then the event that node, can attach a listener and handle it. So, for example you can say that if you have this button, if a user clicks on it, you actually want to log something to the console. So one of the easiest and most straight forward ways of doing this is, is just to actually use the HTML on event attributes, and just write some JavaScript in the middle of your HTML, and then whenever that node has that click event fired, that JavaScript will run. But sometimes you wanna do things more complicated, and you don't necessarily want to inline your JavaScript in your HTML for a variety of reasons, so you might also be familiar with doing this by say giving the button some kind of ID or a cost name, then looking it up, with just plain vanilla JavaScript ways and adding an event listener, or maybe you getting really fancy and you're using J Query and doing the exact same thing. But all of these are just adding DOM event listeners, using just straight out-of-the-box DOM events API. So, there's also really key concept here of propagation, which is the way that we bubble events up through the DOM tree. So, whenever an event happens on a particular node, all of it's parents have a chance to handle that same event. So, for example if we have a parent and a child node, and they each have event listeners, and we click the child node, first, it's event listener will fire, and then the parents. And so, this is really great for cases like say, a button that also has an icon inside of it. If your username is just a click exactly on that icon, you probably still want the button to fire, and you probably don't wanna have to reinstall all your event listeners on the icon, or anything else inside of the button. But sometimes, you don't want this to happen, so you can always just call stop propagation on the event object that is passed to your event listener. And in that case, when you click on the child node, it's even listener will fire, stop propagation, parent listener does not fire nothing above the chain, everyone knows if that event happened. Okay, so all of that which is sort of standard DOM JavaScript in HTML events. So, what about Ember Event listeners? This is the sort of preferred way that we have of handling events and Ember, and it gives us access to Ember-specific state, when we're handling DOM events. So, for example instead of just logging hello, we can use an actual template helper, and gonna say hello. But this on its own is pretty meaningless, so more specifically that's inside one of your template files and then in the corresponding controller component, we actually are defining an action called, say hello that's going to do whatever we want, but since we have access Ember city, we can make this even fancier, we can say that we're going to log whatever text we have currently and we can change that around. One of the nice things with Ember, and this template helper, is that we also have access to some Ember obstructions that set on top of these events. So, say for example we wanna stop propagation. Instead of having to get access to that actual DOM event object and cause stop propagation on it, we can just say bubbles equals pulse, in our template helper, and everything will work the way that we expect it to. So, now that we have all of these different pieces, let's see how they actually fit together, and how DOM event listeners, and Ember Event listeners can interact. So, when an event happens, on a page, it's running on Ember application, all of the DOM event listeners are going to fire first, and then all of the Ember Event listeners. So, more specifically, when we're working with our DOM Event Listeners, we're going to start out at the target node, the thing that the event actually happened to, and then work through all of its parents. Along each step, we're going to see if there's any DOM Event listeners, fire them. And if any of them stop our application, we immediately stop handling the event, do not pass go, do not collect $200. If we get all the way through that process, we start all the way back, at that target node again. But this time we're looking for Ember Even Listeners, walking all the way up, through that same parent chain, and stopping propagation if any of those decide to stop propagation. So, one key difference here, is that when we're working with our DOM even listeners, it's actually the browser, that is using DOM APIs, to call the event listeners and decide what should be called when. And when you're working with your Ember Event listeners, is actually the Ember JS framework code, that is using its own internal APIs to decide what should be called and when. So, the way that that actually works, is really cool. So, you might think of your application is just consisting of these two DOM nodes. But when Ember is actually rendering, everything from your templates into the actual webpage, it puts all of it inside this root element. It's usually the body of the document that everything else goes inside, but you can override it, but the really great thing, is that Ember actually instals it's own DOM Event listeners onto that topmost parent element, which means that any events that are propagated up from anywhere inside of your app, will ultimately hit that root element, firing off the event listeners that Ember has installed, which kicks off its own internal event process. So, this part is really exciting, but if you don't care very much about Ember source code, you might wanna go ahead and just zone off for the next five minutes, because I went on a little bit of a rabbit hole with this one, but the way that Ember actually instals, all of these event lists are just supper cool. So, if you look in the event dispatcher file, in the Ember source code, you can see this giant map of all the DOM events that Ember actually cares about, and so when the app is installing, and being initialised, it actually calls set up handler on that route element for each of these events. And what that does, it just adds that J Query Event handlers. Using some syntax, that I had not seen before, and that really confused me. So, let's take a little bit of a closer look. Specifically, what we're doing here, is we're just using the .on Event handler to add some that event listeners to that root element. The first parameter is specifying, in this case an event might be a click, and it's giving it a name space with Ember. It's not actually changing what event it's listening for, its just sort of naming the listeners so that you can easily remove it later. The really interesting thing is the second parameter, the CSS selector, that's just regular CSS selectors, right. So, what that means is that we're actually using a Delegated Event Handler. So, instead of listening for click events, on the actual root element, what J query is actually doing, is whenever an event bubbles all the way up to that route element, it's going to call our handler, once, for every single child in the chain, that goes from the target, to that root element that matches the CSS selector. So, the same click event, can actually fire hundreds of times, if there's hundreds of ancestors in that chain, between the target node that actually fired, and the root element where Ember has installed it's listener. What this also means, I mean this is all from the J Query docs, which I highly recommend you go read, cause it's really cool, I actually really enjoyed reading like the full text of these docs. I like always give over that section, but this is a really good read, I swear. Is that we can process events from descendant elements that are added to the document at a later time. Which means that we only have to instal those event listeners one time when the application is booted. It doesn't matter what we actually add to our application, or removed, or destroyed, we never need to change those events listeners, because when the society wants to fire, it's actually deciding that, at the time that the click actually happens or whatever other event based on their CSS selectors. So, you can come back now, if you decided hit twitter, when I went on a rant. The really important thing that you need to know from this section, is that Ember is using DOM Event Listeners, that it instals, when it initialises your app, in order to kick off its own internal events process. So, let's walk through an example, and see how this works in practise. So, let's go back to our parent and child node, so if we click on that child node, the first thing that's going to happen is we're going to have the browser start looking for click event listeners, that are DOM Event Listeners at that child node. It'll find one, and fire it, and then walk up to the parent, look for any DOM Event listeners on the parent node, fire those, and then walk up to our Ember route element, which has those special Ember Even Listeners that we just talked about, that are going to kick off our Ember events process. So, now we're gonna start back our title node, look for any Ember Event Listeners, fire those, walk up to the parent again, look for any Ember Event Listeners there, and fire those. So, the really important thing to take away from this slide, is that you can only stop propagation to event listeners that are going to fire after you, which means that my Ember Event Listeners, are never going to be able to stop propagation to my DOM Event Listeners, because the DOM Event Listeners always fire first. Even if the one that's writing first is a parent of the Child Event Listener that we care about. So, it seems like the difference between the Ember Event Listeners, and the DOM Event Listeners is pretty important, but how do we know which one is which? So, let's take a look at some code examples. The first one is our lovely closure actions, instead of new hotness and Ember. So, this one is actually attaching a DOM Event listener, even though it looks like it's using that Ember action helper, so what this is doing, is it's actually wrapping all of your Ember application state, into a pure JavaScript function, that's being handed off to the DOM. So, even though you're using that action template helper, because you're passing that JavaScript function, to the DOM, you're attaching a DOM Event Listener. So, anytime you see that on event HTML syntax, in your template, know that you are using a DOM Event Listener. Likewise, if you're actually using straight up J Query, to attach event listeners somewhere in your Ember application code, that's your actual DOM Event Listener, and it's going to behave like one. Pretty much every other way of attaching events, is an Ember Event Listener. So, if you're just using the action template helper in the middle of a DOM. using sort of like the element modifier re-space and syntax, that is an Ember listener, if you do the same thing but override the particular event that you care about, if you define a function on one of your components that has the same name, as one of the DOM Events that Ember cares about, that is an Ember Event Listener. If you do the same thing in the JavaScript, instead of in the template, that's also an Ember Event Listener. Everything, except for actually just straight up attaching like J Query Event listeners, or using closure actions, are all Ember Event Listeners. So, let's go back to my original bugging code example, and see if we can figure out what went wrong. So, we can see here now, that we have a Child Event Listener, that is trying to stop propagation, by setting bubbles equals false, and that's an Ember Event Listener. But the parent is actually a DOM Event Listener, which means that, when we actually click on the button that should advance the tour, it is the parent that fires first, and then the child. The child never has an opportunity to actually stop propagation. So, the way that I fixed this, was just making them both Ember actions. I was working on some code base that had some shared code, and so I really couldn't change the inner ones, so this is the easiest fix. And what this meant, was that, when I click on next step of the tour, the child now fires first, because they are both ember actions, it stops propagation, and everything worked exactly the way that I wanted it to again. But there is a tonne of subtle weird bugs that you can get into, if you use a mix, of Ember Event listeners and DOM Event listeners. And we're gonna try out a more of them. So, it's Pop Quiz Time! So, we're gonna walk through some code examples, and we're gonna see if you can figure out what you need to do, in order to fix the events bug. First one. That seems like a tricky bug. So we have this parent action, that should not fire, and then we have this child action, and it does say bubbles equals false. This is totally going to work right. So, when we click on the child, the first thing that fires is, this child DOM Event Listener, because DOM Event Listeners always fire first right. The only problem here, is that bubbles equals false, as an Ember obstruction, and we're passing that to a DOM Event Listener. Our Dom Event Listeners don't care about Ember, bubbles equals false, is meaningless here. So, the event propagates anyway. We have a couple of different ways of fixing this one. The first is that we can just make this child action into an Ember Event Listener too, in which case our Ember obstruction will work, so when we click on the child node, it will handle the click, bubbles equals false does have meaning in this context, and so it stops propagation. Our other option is to go all in, on our closure action, in which case we want to stop propagation in the actual event definition where we redefined it. So, we do have access to that actual DOM Event object and we can just call stop propagation on it there. This will also work, because once again that DOM Event Listener will always fire first, and then it will call stop propagation in it's action handler, and it will not bubble. Okay, what about this one? That's gotta be awkward if you wanna call stop propagation on it. So, how do you fix this one? In this case we're gonna have a controller, that has an action that's just gonna log out the arguments that it gets when it's called. And so, the first implementation we're gonna try, is just using this element modifier syntax, where we put that action template handler in the middle of the DOM node. And so, when we click on the button this time, we get nothing. we have no arguments, we do not have access to the DOM object, the event object that fired this click, whenever it actually happens, when we're working with this element modifier syntax. We do have access to it though, if we use a component. So, if we just do a component click like this, we do get that original mouse event. But what if we want to pass, some other kind of functions and parameters to our action handler, do we lose access to our original DOM Event? So, say this time we gonna also pass hello, and maybe we are gonna log that out somewhere. In this case, when we fire that event, we actually do get access, to our original DOM Event that fired this click, it just appended at the very least of any other parameters that we pass into our action template helper there. So, it's worth looking back at this list that we were checking out earlier that shows all the different types of events, and realising that every single one of them has access to that original DOM Event, except for the element modifier versions. So, if you are working with that element modifier syntax where you're just putting an action template helper, in the middle of the DOM node, you are going to have to use the Ember obstructions like bubbles equals false. You do not have access to the original DOM Event that caused that click to happen. Okay, are you still with me? Because I've got one more bug, and this one's a doozy. And I clicked it a lot. So, we have our parent action that we don't want to be firing, we have this child action that's going to stop propagation, and we have this inner child that's going to say hello. And so, when we click on that inner child, we're expecting it to say hello, right? Wrong, so it actually turns out, that because DOM Event Listeners always fire first, the first event listener that fires, is that middle one. It stops propagation, we don't say hello. So, let's try fixing this, by making that an Ember Event Listener. This is totally gonna solve all of our problems right. So, this time, actions do fire in the order that we think they're going to fire. We say hello, then we call stop propagation. The only problem is that our event is no longer pass into our action, so we can't actually call stop propagation on it, so that top level parent is going to fire. So, to really fix this one, what we're actually going to have to do is use bubbles equals false to make this an Ember Event Listener. And this time when we click, we're going to call, say hello, we'll handle our click, we stop propagation using our Ember obstruction and everything is great. So the take away here, is just to pick one event listener APIs and stick with it. When you start combining them, and like handling events in the same place, things get messy nasty really quick, in ways that you probably didn't expect. And a lot of these examples that I've been using, I've been picking Ember Event Listeners, over DOM Event Listeners, in large part because the cod bies that I work with already has a whole bunch of them, and so it's easier just to standardise there. But you may have other reasons for choosing to go all in on DOM Event Listeners. For example, if you really wanna use closure actions all of your closure actions the way that you normally think of using them, are gonna be DOM Event Listeners. So, you should just go all in on DOM Event Listeners. But in order to inform your choice of which strategy you wanna pick, you're probably wondering at this point, what about performance? Is one of them faster than the other? Is one of them better than the other, which one should I pick? So, I saved you a click, and I did a lot of performance profiling with Google Chrome, and I built a very small Ember app, that adds a whole bunch of components to a page over time, that uses each of these different types of event listeners. So, for just a regular DOM Event Listeners, using a closure action. The really interesting thing that we see here, probably shouldn't be a surprise, is that we add a new event listener to the DOM, with every component that we add, which makes sense, because we're attaching DOM Event Listeners, so those event listeners actually do have to be handled by the browser. so it's a one-to-one ratio here. For our element modifier listeners, we're just using that E space syntax. Our event listeners, actually stay constant no matter how many components we add to the page, because we're reusing those same Ember Event Listeners that are detached when it first initialised our application. It doesn't need to add new ones to the DOM, every time it adds a component. And the same is true with our component listeners once again as we add more components to the page, the number of DOM Event Listeners stays the same, it's just reusing the same Ember ones, it's all delegated, which just to drive that home, that doesn't mean that the number of event listeners in the DOM when you're working with Ember events, is much smaller than when you're working with the DOM, DOM Event Listeners. But if you're not keeping track of those event listeners in the DOM, that probably means, you're keeping track of them in memory. So, how much more memory is Ember requiring to actually keep track of all of those event listeners that it's managing? It does actually use more memory, that's probably not a surprise, but it's not so much that it's like really upsetting to me, I would consider this like acceptable. I'm not really sure about the different trade-offs, I didn't really see a big performance, or like a usability difference between having a bunch of DOM Event Listeners versus having extra memory on the heap to internally manage event listeners. So, I don't really see a clear winner, I'm just in terms of these, but what about speed? I'm sure some of you are wondering that if all of you are Ember Event Listeners have to be delegated does that mean that they take longer to fire? No, everything is still fast, it really doesn't make a difference. I tried benchmarking the time between when I actually fire a click, and when the click event listener happened, an average at over hundred times each of these, and they're all under like 2 milliseconds, so you really don't have to worry about speed. So overall, it's not really clear which type of event listeners more perform it, one of them, DOM Event Listeners, does add more work to the DOM, the browser has to handle, and the other adds more work in terms of memory that Ember's handling internally. So, I don't really say that there's one you should pick based solely on performance. I would really commend, that you should pick the event listener strategy, that is going to allow you to write really consistent clean and correct code. And then if you're really seriously noticing usability problems, that you really think is based on the event listeners, that you're using inside of your app, maybe consider switching based on performance reasons then. But, for every rule, there's always an exception right, and so I've been going all in on using Ember Event Listeners, but what about this feature request? What if you want to optionally add a listener? Turns out, this is kind of hard to do with just Ember Event Listeners. So, specifically if I have some button or div, and I only want it to be clicked, when it's in some sort of active C. In an ideal perfect world, you could just say, if it's active, then add this action. That's a compiler area, you can't actually use the F template helper, in that element modifier syntax. So, the first option here, is just to split everything into separate conditional blocks, which definitely does work, and I kind of like this, because it helps make it much more clear what your different states are, but it can also lead to a lot of redundancy in your code. So, another thing that you can do, is to just exit really from your handler, if you don't wanna actually handle anything. This may also meant that you wanna modify your styles, so that when it's not in the active state, it doesn't look clickable. But then that also starts to feel like it's not really accessible , and it's kind of confusing. So, probably the easiest way to solve this, is just to use a closure action, because since you are just optionally passing a function to that on click equals attribute, you can use that if template helper, the way that you really want to. So, even in cases where I normally use Ember Event Listeners, I usually used closure actions in DOM Event Listeners for this case, just because it makes it so much simpler. So, take credit, whatever you think is best for you. Which is really the take away here, that you are the one has the most information about what you need to solve for your use case, and so what I've really been hoping, is that this talk just gives you all of the information that you need to make the best decision for you. So, if there's only two things that I want you to remember from this entire talk, the first is that DOM Event Listeners always fire before Ember Event Listeners. Always, always, always. And the second, is that you really want all of your event handling to be as consistent and predictable as possible. It just leads to so many headaches when you start mixing and matching your event listeners. So, I hope that now, you really feel that you can debug with clarity and implement with confidence all of your event listeners. If you have any questions or comments please reach out to me, I'm @mariechatfield on twitter. This talk was inspired by a blog post that I originally wrote for Squares Engineering Blog. You can find links on my website, and I'll also be around for questions afterward, if you have any, but I am not a huge taking questions from the stage, so thank you so much for your time.