Posted on September 29, 2014 by Chris Harrington

Animated rotate directive with Angular JS

I’ve recently been playing around with Angular JS and I like it, and so decided to spend a bunch of time converting Leaf from Knockout to Angular. It’s not that I think that Angular is substantially better than Knockout (that remains to be seen) but rather that I figure this is a good way for me to figure out the ins and outs of a new framework. One of the things I really like about Angular is building custom directives. I’ve only been tinkering for a week or two, but I already have dozens of custom directives for all sorts of situations and code snippets. They’re very, very handy.

So in that vein, one of the directives I’ve built revolves around rotating an element based on an Angular scope variable. My first use case for it was for expanding a sliding drop down. In the collapsed state, there’s a Font Awesome angle down icon that indicates to the user that it can be expanded, and after expansion, it changes to an angle up icon to indicate that the user can click again to collapse the panel. The following rotate directive is used to animate a rotation from up to down.

Note: I’m going to assume that the reader is at least somewhat familiar with Angular JS. If you’re not, check out Angular’s home page for a quick rundown. Same goes for Font Awesome.

First, the HTML. Brace yourself; it’s super complicated.

<i class="fa fa-angle-up" rotate-flip="rotated"></i>

I’ve left out the wrapping HTML which sets up the Angular app and controller just for brevity’s sake. The CSS classes are Font Awesome specific. “fa-angle-up” is just a caret. The fun part of the tag is the custom attribute “rotate-flip”. An Angular directive can take a couple of different forms, the most popular (at least for me) are new tags () followed by attributes as we see here. The directive is specified in code as such:

angular.module("demo").controller("demo-controller", function($scope) {
	$scope.rotated = false;
}).directive("rotateFlip", function() {
    var first = true;
    return {
	restrict: "A",
	scope: {
	    flag: "=rotateFlip"
	link: function(scope, element) {
	    scope.$watch("flag", function() {
		element.toggleClass("rotated", scope.flag);
		first = false;

The first line here is defining the Angular controller, which sets the rotated flag. After that is the directive. Note that the directive name (“rotateFlip”) is camel case, while the attribute name in the HTML is written in all lower case with dashes (“rotate-flip”). The “first” variable is there to skip the animation after setting the watched variable for the first time. The directive is restricted to attributes only, which is indicated by the restrict: “A” line, and the only thing in our scope is the flag to indicate when the element should be rotated. The link function watches a change in the variable given in the HTML, which in this case is “rotated”. We’ll see what that hooks up to in a sec. The watch will basically toggle a CSS class to perform the animation. The class definition is as follows:

[rotate-flip] {
    -webkit-transition:transform ease 250ms;
    -moz-transition:transform ease 250ms;
    -o-transition:transform ease 250ms;
    -ms-transition:transform ease 250ms;
    transition:transform ease 250ms;

[rotate-flip].rotated {

Note: This right here is an excellent use case for switching to LESS or SASS, which both allow you to define mixins that would encapsulate stuff like this so that you only have to define vendor-specific CSS rules once. Writing out all of these rules is pretty annoying without them. These rules set up the rotation transform using CSS3. The default direction for rotation is clockwise, but if you want counter-clockwise, you can change the degree amounts to a negative amount. For example, rotating to the 9 o’ clock position counter-clockwise, the degree value would be -90.

And that’s basically it. Angular directives are super powerful and makes for modular code that’s easily maintainable. I’ve put together a little demo to showcase what I’ve done here. Take a look. Thanks for reading!