Animating the Angular Way

AngularJS - Animating the Angular WayAngularJS Video Tutorial by John Lindquist

In this episode John uses TweenMax to add animations to AngularJS. Instead of manipulating DOM directly in a controller, we will use an AngularJS directive to abstract that view logic cleanly using $animate.


The ability to reply to discussions is limited to PRO subscribers. Want to join in the discussion? Click here to subscribe now.
egghead.io
Avatar

In this episode John uses TweenMax to add animations to AngularJS. Instead of manipulating DOM directly in a controller, we will use an AngularJS directive to abstract that view logic cleanly using $animate.


index.html

<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="utf-8">
  <title>Egghead Videos</title>
  <link rel="stylesheet" href="vendor/bootstrap/css/bootstrap.min.css">
</head>
<body ng-app="app" ng-controller="AppCtrl as app">

  <button class="btn-primary" ng-click="app.fadeIt()">Click to fade</button>
  <div class="badge" hide-me="app.isHidden">Fade me</div>
  <div class="badge" hide-me="app.isHidden">Fade me</div>
  <div class="badge">Fade me</div>
  <div class="badge" hide-me="app.isHidden">Fade me</div>
  <div class="badge" hide-me="app.isHidden">Fade me</div>
  <div class="badge" hide-me="app.isHidden">Fade me</div>

  <script type="text/javascript" src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
  <script type="text/javascript" src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
  <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.2/TweenMax.min.js"></script>
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.6/angular.min.js"></script>
  <script type="text/javascript" src="vendor/angular/angular-animate.min_1.2.6.js"></script>
  <script type="text/javascript" src="app.js"></script>
</body>
</html>

app.js

var app = angular.module("app", ["ngAnimate"]);

app.controller("AppCtrl", function() {
    this.isHidden = false;
    this.fadeIt = function() {
        this.isHidden = !this.isHidden;
    }
});

app.directive("hideMe", function($animate) {
    return function(scope, element, attrs) {
        scope.$watch(attrs.hideMe, function(newVal) {
            if (newVal) {
                $animate.addClass(element, "fade")
            } else {
                $animate.removeClass(element, "fade")
            }
        })
    }
});

app.animation(".fade", function() {
    return {
        addClass: function(element, className) {
            TweenMax.to(element, 1, {opacity: 0});
        },
        removeClass: function(element, className) {
            TweenMax.to(element, 1, {opacity: 1});
        }
    }
})

John Lindquist: If you're used to using jQuery, you'd probably take an approach like this to fade out an element to where you find the element you want to click, and then find the element you want to fade. You can see that this works just fine. Now this isn't the Angular Way, and in this scenario, the Angular Way actually takes a lot more code, but I'll show you the benefits of why.

A lot of people think that the Angular Way would simply be just setting up a click on this, an NG click, and just saying, "Fade It." If we do this, "Fade It" and then on the fade it, you might think I'll just grab that, and then I'll look up the element inside of the "Fade It" function, and then just tween it from there.

Now you'll see that this works just fine, and you may think OK, you're done. But, typically manipulating the DOM, refining elements inside of a controller is a bad idea, for usability and other reasons, which I'll get into.

I'm going to switch over to the Angular Way, which is actually to use a directive. I'll jump into here and I'll just create a directive called...we'll just call it "Hide Me." A simple name and we'll say "Hide Me" we'll say app is hidden. When we say "Fade It" now, I want to say is hidden and flip the current value.

I'll just start this off as false. Now, let's just get rid of this click. Now, nothing would happen because I haven't added the directive yet. I'll add the directive is...what did I call it? I called it "Hide Me." "Hide Me" and then just return a linking function with the scope, element, and attributes.

Then the scope watcher can actually watch the attributes. It's going to watch the attributes "Hide Me," which is the name of the directive, and it'll take the new value and then based on that new value, we can actually say if the new value is true, then you might think now, I can just look up the new element from here and then I'd be done.

You see this is working, but that's not the way you want to do it either. You might think, "Oh, now that I have the element, I'm not looking it up anymore." This is much closer to the Angular Way, so that's still working, it's just that Angular has now introduced the animator service, which allows you to extract animations as well, so we'll just get rid of that all together.

We'll say animate, add class, and whenever this class is added, we'll add it to the element, and we'll just call it "Fade."

In the animation service, we'll create a class called "Fade" and return the object that has add class on it, and add class passes the element, and the class name as you can see above. From there, we can do exactly what we did before. You'll see this now is still working.

There's also a remove class, obviously to duplicate this, and change this to remove, and then change the opacity back to one. It'll fade back in, so you can say if the new value is false then, let's copy that and paste it, so instead of add class, we'll say remove class, and you can see that we can fade out and back in. Fade out and back in.

Now what this allows us to do, the reason we did it this way is that now the HTML is descriptive. We know that something is happening when app.hidden is changing. We know it's going to use that "Hide Me" directive when app.hidden is changing.

We can also duplicate this and use it multiple times. We don't have to worry about looking up the element, say click to fade, click to fade. I can actually remove the directive from one of them and refresh and you can see all of them will fade except for that one. Or I can actually change what "Hide Me" is bound to, and what it's watching for to change and fade on a completely different value.

Also, you could take this entire app controller, duplicate it, reuse the directive across multiple projects or there's many, many ways you can approach this. That's all things you would do in the HTML, because as far as the java script is concerned, it's done at this point. You have a directive, you have an animation, you can reuse them, and you don't have to touch this code anymore.

It's all about declaring what you want to do in the HTML. That's why they say AngularJS takes a declarative approach because you're saying, "I want this to hide, when app.hidden changes." That's what the Angular Way is.

One last thing to mention is now that we've done it this way, we can actually get rid of all of these ID's and we're not going to have to use them anymore, because we're using directives, and we simply don't need ID's anymore in our code. Actually, what you'll notice is that ID's turn into a code smell, because they mean that you need to look up that element using java script.

Tech logo bar

Don't miss out on the latest PRO lessons.

Get Your PRO Subscription Now

Because you like code... not PowerPoint slides.