Using Aspect Oriented Programming (AOP) techniques, you can easily decorate AngularJS controller methods to add additional behaviors. This can be useful for handling analytics and other common concerns in a typical application.
Trevor: We can use an Angular factory that returns a function to decorate a scope. In this case, we're going to try to call this afterMethod after a certain function is on the scope. Take a look at this version here. The decorator is called with the scope and then the two function names. Both are defined up here. One takes arguments, one does not. They both increments values on the scope and return values.
For the purpose of testing, we also have a scopeBefore, as in a scopeBefore it's decorated. We make a quick shallow clone of that to validate behavior. For the afterMethod, we're just going use a spy here to validate that it's getting called when we want it to.
Taking a look at the tests quickly, we validate that before. It never calls the afterMethod. This is important just to know that it's working properly, and then we want to validate just before and after things are working as expected, so that plain works the same as it did before. It increments the same value. The signature, the toString method, is the same, and then also that it called the afterMethod, or afterMethodSpy in this case.
We do the same with the withArgs function and it also gets called with arguments.
We're going to run these tests real quick, and in a second we're just going to focus on the plain ones, just to shorten the scope. We have two failing. This makes sense because the two failing are actually this and this. Neither is calling the afterMethodSpy because all we're doing is just returning the scope without any direction. Let's focus on these and that should get us most of what we're looking at here.
First we want to focus on this concept of decorating a method, and I'm going to create a separate function for this. It's going to be called createWrapper, and createWrapper is going to take in a function that is the function we're decorating. It's also going to return a function wrapped around that, and then it's going to return the value of that fn.apply(fn). That is just a way to wrap it really quickly.
Now, because we want the afterMethod called, and we want this returned, we can set a variable here called the orig, and then we're going to apply the afterMethod, and then we're going to return the orig, so what we're doing is calling the initial function first in the afterMethod and then returning what that one would turn in with. All the tests are still doing the same thing because the decorator isn't using this.
This is a pretty simple way to create a wrapper, and if we apply it to the functions as they're defined here, we do it in a for loop, we'll say fName, let's do a quick if check to say that the scope fName is actually a type of function, and then in here, we want to override this value using the createWrapper method, and we override it passing in itself. OK, cool.
Now we have one failing, but it's actually a different failure. It's this toString case, which is interesting, so one of the things we're going to have to deal with here is the fact that this function is going to have a different signature now that it's a new function, but we actually want it to have the original method's signature.
A way to do this is also just to say newFn, and then we'll say newFn.toString, so we're going to override that method as well, and it's going to equal that guy, so we're going to just return this value. That's just kind of proxying down to it, and then when we return newFn we'll have everything we need. Cool.
All four of the tests in the plain section are passing, so we're iterating over this, overriding this function, giving it a new signature. Let's check out withArgs.
That's a really quick way to decorate a function and then call another function right after it while maintaining the same initial behavior of the method.