Events are a useful tool for triggering functionality based on something that has occurred in your application. In this lesson, we will build upon the concepts from the create a scope decorator lesson to create an event decorator that can be used to cleanly compose functionality on top of Angular events.
In much the same way we can decorate scope methods we can decorate the event listeners on the scope.
If you haven't watched the "Creating a Scope Decorator" video, I recommend watching that for some context. I'm going to take that same approach with this process and we're going to test drive it.
Here we have a spec for the different event handlers. We have the afterMethodSpy like we do in the scope decorator video. Each event handler has its own spy.
You can see here we have an event without data, an event that we're not going to decorate, and an event with data. We have the before case where the event without data and the event with data make sure they don't call the afterMethodSpy.
Then we have the after case where we decorate. These are the two events that we specify. We want to make sure that the original handlers are called. We want to make sure they're called with the event object.
We don't know exactly what that object is going to look like so we use the jasmine.any helper, which is a good way to say, "Yeah, I know an object's going to be passed to it but I don't really know what it's going to look like exactly." It doesn't really matter because Angular is going to generate that and we just trust the framework to do it. They have their own unit test.
The same with the after method. It's going to get called with the same exact event. You want to make sure that the after method is called in the post decoration case.
You also want to make sure that in the case of an event with data that the after method also gets the data argument so we're doing that assertion here.
We want to make sure the non-decorated event, although the original handler is still called after decoration, the after method is not called.
Finally, we want to make sure that the return scope is equal to scope, ie the decorator returns the scope.
If we run all these we're going to get a few failures. The three failures shouldn't be that crazy. They're this case, where we're not returning anything yet and the cases where the two after methods, this one and this one, are called because we haven't built in that functionality.
We're going to grab the create wrapper function from the scope decorator. It may be good to externalize this and use it in all three cases later but for now we're going to make it custom to each. We can do that in a later video.
We're going to iterate through the scope listeners property. This is $$listeners. This is actually a hash property. Each one's going to be an event name. Then we're going to do something that's a little bit of a no-no. It's the double iteration.
The reason is because each of these names is actually an array. What we really want to do is deal with everything in the array.
Each name refers to an array of handlers, whether it's one handler or 10, it doesn't really matter. You can have as many event handlers as you want for any given event on the scope. This is the way that Angular stores them.
You may want to go with a more efficient process in the future but this should do the job for the moment, especially because we're only instantiating two of these.
What we're going to do is create the wrapper around the event. That's cool.
We created the same wrapper the say way we did the scope and watch decoration. We've got two cases failing. One of them is the simple expected undefined to equal...Yeah, that's the case where the scope needs to be returned here so we're just going to return the scope. Simple enough.
The other case is where the afterMethodSpy is not to be called. In this case we're actually just decorating every event. What we want to do is only decorate the events that are actually in our list.
We want to say if (events.indexOf(eventName) !== -1), in other words it's actually in the events collection then we know to do that decoration. This should do the job for us.
We've created what is definitely probably not the most efficient function ever created but it's taking a look at all our listeners and decorating the ones we specify. We certainly could make this more efficient in a further iteration.
The ones we're specifying are right here. That's making sure the afterMethodSpy is called after each one.
Let's take a look at a practical usage of this decorator pattern. I have here a simple Angular app with two sections, a firing section and a catching section.
Firing fires off events and the catching section catches those events. It listens for them.
Let's take a little look at the architecture here. The firing controller just sends off events and lets you select between the events in the drop down. Catching catches those events and logs a message. It also uses the event decorator to decorate the scope. In this case, we're decorating the user logged in and user logged out events.
The next panel is a third party analytics library that we're using in our after method. We'll go into more of that but it's basically like Google Analytics. It's similar to that and has an API call it makes out to a network.
The next panel, tracker, is the actual function that we are using in place of our after method. It is taking the arguments given to it by the event and grabbing the name off the first argument, which would be the event object.
I'm also doing a log here because the next panel encodes its event names.
Finally, the event decorator, which you've already seen. I've replaced the after method with the mixed panel tracker here. We're actually providing some context for this. It's not some nebulous after method.
Let's take a look at how this works in the context of these events. You can already see that the console has fired off these. If we fire off logged out, we not only get the console message here but we also get the mixed panel method firing off. That would be the mixed panel track method.
Same thing for logged in.
This is a great way to compose these functions and this analytics so that they're not necessarily effecting each other or built into the same controller but they're actually composed in such a way that they can still behave the way the application needs to.