This post is for all the designers in my life. I love AngularJS because as a developer it just makes sense but I am certain it is an entirely confusing and brave new world for some people.
The Backstory
This post was inspired by a series of conversations that I have had with one of my best buds, Shane Mielke, over a project that I am helping him out on. So before we go any further, I encourage you to check out his protfolio at http://www.shanemielke.com/work.php and you will see that he has some serious chops. He is rock solid when it comes to jQuery but it gets totally fuzzy when it comes to writing that same code in AngularJS.
I have a TON of friends who fall into this category. Awesome designer? Check! jQuery skills? Check! AngularJS? Uhhhhhh…. what?
So! I am going to show you a very gentle way to take something you would write in jQuery and convert it into something that doesn’t explode the AngularJS universe.
jQuery Events
For starters, it is important to mention the relationship that AngularJS has with jQuery. AngularJS comes with a subset of jQuery right out of the box. If you have included jQuery in your document then AngularJS will defer to that version of jQuery. This kind of cooperation right out of the gates is really handy.
I am going to give you the condensed version immediately so that if the light goes on and you cannot wait to go write code, you are not obligated to finish. I love designers as much as AngularJS does. Believe dat!
The simplest way to convert jQuery code into AngularJS is to create a directive and put that code in the link function of that directive.
This does not solve any architecture problems per se and there are some ways to optimize your directives but! this is going to get you started and the side effect is that you will have portable code. Watch this magic trick!
Let us take the click example from the jQuery docs via http://api.jquery.com/click/ and convert it into AngularJS.
First Paragraph Second Paragraph Yet one more Paragraph <script> $("p").click(function () { $(this).slideUp(); }); </script>
So we have all seen some variation of this theme a gazillion times. Target some HTML element and add an event handler to it that does something. The stage has been set.
So first things first, we bootstrap the AngularJS application with ng-app and the module name we want to load.
<html ng-app="myModule">
And then we hint at something amazing.
var myModule = angular.module('myModule', []); myModule.directive('slideUp', function () { var linker = function (scope, element, attrs) { // WAIT FOR IT! }; <pre><code>return { restrict:'A', link:linker } </code></pre> })
So I have created a directive called slideUp and we are going to put some DOM manipulation code in place to fulfill its destiny. You may want to sit down for this.
myModule.directive('slideUp', function () { var linker = function (scope, element, attrs) { element.on('click', function () { $(this).slideUp(); }); }; <pre><code>return { restrict:'A', link:linker } </code></pre> })
What the!? That looks almost like the jQuery code. True story bro! The difference is that the parameter element is already a jQuery object so we don’t have to query it via $( ).
That is all nice and wonderful but how do we use it?
<p slide-up>Second Paragraph <p slide-up>Yet one more Paragraph
Special Note: AngularJS converts camel case directive names into snake case and so slideUp becomes slide-up.
So that was easy! But here is the really cool part. You can now use that directive to ‘decorate’ other elements.
<p slide-up>Second Paragraph <p slide-up>Yet one more Paragraph <div class="box" slide-up></div>
With no modification, I simply added slide-up to my div tag and it worked exactly the same. This is what I mean by ‘portable’.
A Few Variations
I am going to show you a few quick variations to get the wheels turning. I am only going to explain the interesting parts of each example and not belabor the redundant bits.
The next series of examples are going to be built around jQuery click event and the animate function. The premise is fairly simple. I want to animate an element in some direction when I click it.
For reference purposes you can see the full code here:
https://github.com/simpulton/angular-designers
Let us create a directive called animateRight.
myModule.directive('animateRight', function () { var linker = function (scope, element, attrs) { // So far so good. }; <pre><code>return { restrict:'A', link:linker } </code></pre> })
Lets add in the click handler.
myModule.directive('animateRight', function () { var linker = function (scope, element, attrs) { var right = function() { // Well THAT was easy } <pre><code> element.on('click', right); }; ... </code></pre> })
And the animate part.
myModule.directive('animateRight', function () { var linker = function (scope, element, attrs) { var right = function() { // We move it right by increasing the distance on the left - go figure $(this).animate({ left: '+=150' }) } <pre><code> element.on('click', right); }; ... </code></pre> })
And this is how we decorate the DOM.
<div animate-right class="box"></div>
And for fun let us create a variation that moves the element down.
myModule.directive('animateDown', function () { var linker = function (scope, element, attrs) { var down = function() { $(this).animate({ top: '+=150' }) } <pre><code> element.on('click', down); }; ... </code></pre> })
So you may have noticed this bit of code that says restrict:’A’ and wondered what in the world that is for. That essentially tells the directive that it can only be used as an attribute. You can also use E for element and C for class.
And since I brought it up, let me show you how a class directive works.
myModule.directive('animateRightClass', function(){ ... <pre><code>return { restrict:'C', link: linker } </code></pre> })
Change A to C and give it a new name for fun and drop it in your HTML. Bam!
<div class="box animate-right-class"></div>
And one more example for the road! You may have noticed the scope and attrs function being passed in to the link function. Do they actually do something? Well yes they do! Let us create a directive of the more dynamic variety.
<div animate="right" class="box"></div> <div animate="down" class="box"></div>
We are now passing in the direction that we want the element to animate.
Let us create the animate directive.
myModule.directive('animate', function(){ var linker = function(scope,element,attrs) { // BY NOW THIS HAS GOT TO BE BOOOOOORING! }; <pre><code>return { restrict:'A', link: linker } </code></pre> })
Scope is the current scope of the directive and attrs is an array of all the attributes on the element the directive lives on. So in the example above the way to get the value of what direction you want the directive to animate you simply refer to it as attrs[‘animate’].
We are also going to create some functions on scope that we can dynamically reference in the event handler.
myModule.directive('animate', function(){ var linker = function(scope,element,attrs) { scope.up = function() { // You know what to do here } <pre><code> scope.down = function() { // You know what to do here } scope.left = function() { // You know what to do here } scope.right = function() { // You know what to do here } var direction = attrs['animate']; element.on('click', scope[direction]); }; ... </code></pre> })
My favorite part of all this is that we are playing to JavaScript’s strength and doing some dynamic function referencing which is really cool!
Conclusion
I have walked you through a series of examples that shows how easy it is to pull your jQuery code into a directive and then ‘decorate’ your DOM with that functionality. The upside is that your code now becomes very portable and you can apply it to more than one element without having to go down the path of writing a query for every element you want to target.
Another upside is that because of the way that AngularJS compiles directives you never have to worry about dynamic elements getting event handlers. This is because the element parameter is always a jQuery object to start out with.
Resources
The Code Example:
https://github.com/simpulton/angular-designers
Shane Mielke’s Portfolio [He really is one of my favorite designers]:
http://www.shanemielke.com/work.php
AngularJS Directive Guide [Which I still reference often]:
http://docs.angularjs.org/guide/directive
really cool explanation…I love how u teach the concepts…would be possible in a near future a post about compile and link properties from directive…I can’t understand this so well right now…thanks!!
Hi Angel — can you give me an example? What are you trying to do exactly?
Great article!
Nice article. Created a fiddle to play around with the code you provided. Check it out here: http://jsfiddle.net/dwaynecrooks/9VQxQ/. Thanks again!
Cool! Thanks for doing that Dwayne! Everyone… enjoy.
Nice job! Also instead of $(this) you could simply use element, see updated fiddle – http://jsfiddle.net/9VQxQ/1/
Great explanation! Thanks!
Hey, man, you’re a great man! Thank you very much, you’re like a light of the beacon. My love for AngularJS grows.
I can dig what you’re trying to do here… my only fear is that people not familiar with all with the semantics of AngularJS such as Directives, and Linker, will be lost immediately. I think it could use a little bit more background first to be a “jQuery designer -> to -> AngularJS” article, it’s pretty confusing from the very start… 🙁 Do appreciate what you’re going for though!
Excellent Explanation!!! Thanks a ton 🙂
Excellent explanation – thanks!.
Question on separation of concerns: I’m fairly new to jQuery but I’ve been rolling with it pretty well. I am now considering the move to making my app an angularjs app. When I see ‘slideUp’ in an html tag, I cannot help but feel like the behavior that belongs to a scripting language is creeping into the structural infrastructure of the web-app. How do you reason about separation of concerns when using angularjs?
P.S. Commenters on your blog do not have editing privileges over their posts. Is this by design? I made an error on my earlier post and realized I couldn’t correct it. 🙁
why not to use existing ng-click directive?
You totally could and in most cases I would but I did it this way for demonstration purposes to illustrate how to capture and respond to AN event.
Also, just because there is a directive for the event does not mean that it is the most performant. I recommend checking out this great talk by Scott Moss here https://www.youtube.com/watch?v=wbcJfg-d5nI