DISCLAIMER: This is an experiment, it is not something officially supported and because of that, this is not meant for beginners. Use it at your own risk and take notice that a bad use of it can break the Internet.
Jokes aside, this could be useful in a bunch of use cases. It is up to you to decide.
Ever had a third party directive where you wished it had any extra behavior you wanted? I did.
Let’s see an example:
1 2 3 4 5 6
1 2 3
You think that this directive is awesome (really? :P) but you’re one of those developers that likes to use directives as a comment. The problem is that the directive doesn’t allow it and you don’t see why it shouldn’t. What can you do here? We can decorate it! How? Using the
$provide.decorator we use to decorate services. Really? See:
1 2 3 4 5 6 7 8
What’s going on here? We pass the directive name (with the
Directive suffix) into the
$provide.decorator and then the callback receives the original directive inside an array. We store the directive itself in a variable and we just need to change the
restrict to what we want, AKA restricted to attributes and comments. Finally we just return the delegate.
Now we can do this:
1 2 3 4
The bright side of this way is that we don’t need to create an extra directive to hold our new behavior, we just decorate the original one, so we just need to use it as before, the only difference is that now it has a decorated behavior.
Let’s see another example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1 2 3
A more complicated directive. It receives a name via an attribute and we display it on the template with an exclamation mark.
We got it but we really need to run a function to log how many times a user has clicked on the directive. That means that we need to extend our isolated scope and link function. Let’s go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
First we just added a new key to our isolated scope for the function, then the idea is to extend our
link function with new functionality. To do that, we first hold the old
link function into a variable and then we extend it. How?
link function is just syntactic sugar, we need to create a compile function which will return our new
link function. Inside there, we call
apply in the old
link function to get the old functionality. With that set, we just need to add the extra behavior, in this case we bind the
click event into the element which will call the new function upon click.
We just need to add the following code:
1 2 3 4
1 2 3 4 5 6 7
As you see, now we can use the
fn attribute on our directive and it works as expected.
Works like a charm!
I like this solution, but what happens if we also have a
compile function? Wouldn’t that remove it? Yes, but we can avoid that. Let’s see:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
It is the last directive but now it appends a new div into the DOM. How can we work with the
link function in this case?:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Just the same idea! We grab the old
compile function and we create a new one. Notice that we put proper parameters this time because we have a real
compile function in our directive. Then we call
apply as we did before but since our
compile returns the
link function, we hold it in a new variable. The rest is much the same, we return a new
link function that will be extended with our new stuff.
Working as expected. What about… controllers? Well there are two possibilities. If the
controller is an inline function in our directive, it is much the same, holding the old one, extending it as we did with
But if the
controller just holds the name of the controller it wants to use, the decoration becomes a little bit problematic.
1 2 3 4 5 6 7 8 9 10 11 12
Sure, the directive is good enough, but we would love to change
$scope.name after three seconds to something else. To do that we need to decorate the controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
We assign the controller name (if the controller is inline,
directive.controller will hold the actual controller instead of the name) into a variable and then we create a new controller in our directive. Since we need to use
$timeout we inject it too.
The difference here is that since we don’t hold the actual controller but a name, we need to use
$controller (injected in the decorator) to fetch the actual controller. To make it work we pass the controller name and all the parameters the original controller has, AKA the
Here we can’t use
apply, instead, we used
angular.extend to “apply” the old behavior. Then we just needed to add the new behavior.
There is another way (just the important bits):
1 2 3 4 5 6 7 8 9
Instead of using
angular.extend we just return the old controller at the end. If you need to override old stuff, just use
Two things to have in mind. First: The decorators need to appear after the directives or they won’t find them. Second: If you want to decorate let’s say the
ui-bootstrap you should apply the decorator in a config function on the
ui-bootstrap module, not your application one.
This experiment could be useful in those cases were we have some 3rd party directive that we need to do something else. It is not something for everyday use but I think that the knowledge is worth it.
I remember the day I spent like 2 hours creating a new directive to extend the functionality of the
accordion to log when a user clicks on the header. A lot of DOM manipulation, fighting with
jqLite limitations and finally, we got it working. With this, it is just… 5 lines of code?
I also want to thank my good friend Rodric Haddad who helped me a lot with the brainstorming.