AngularJS with Animation and Callback
Wednesday, Mar 19, 2014
What is the problem
When we use AngularJS, manipulating the DOM element is something we often want to do. I encountered a problem recently that I have an object bound to a bunch of fields in a container, then what I need to do is to bind a new object, but with an fade in and fade out animation. So it is pretty much like this:
- Fade out the container with old object
- Bind the new object
- Fade in the container with new object
How did I solve it
I first went to Google (of course this is what you guys would do as well), and found this article AngularJS - Animating the Angular Way by EggHead. It is a really good tutorial talks about how to define a directive to watch for the attribute and then use the $animate service to do the animation. However, this article did not address how to solve the problem of updating the scope after the animation. So here I just want to quickly give you the way of how to do it.
Actually it is pretty simple, all you need is to add a call back function, and some tricks.
Here is an example, or you can try to open the demo here.
<!DOCTYPE html> <html> <head> <title>AngularJS Animate And Callback Demo :: XYZHOU.com</title> </head> <body> <div id="ng-app" ng-app="myApp" ng-controller="myController" ng-init="init()"> <div fade-me="isFade" ng-click="flip()"> Click this: {{myValue}} </div> </div> <script src="//code.jquery.com/jquery-2.1.0.min.js"></script> <script src="//code.angularjs.org/1.2.14/angular.min.js"></script> <script src="//code.angularjs.org/1.2.14/angular-animate.min.js"></script> <script> var myApp = angular.module('myApp', ['ngAnimate']); myApp.controller('myController', ['$scope', function($scope) { $scope.init = function() { // init values $scope.isFade = false; $scope.myValue = "Foo"; }; $scope.flip = function() { // flip the value only when the container is faded out if ($scope.isFade) $scope.myValue = ($scope.myValue === "Foo") ? "Bar" : "Foo"; // flip the fade value $scope.isFade = !$scope.isFade; } }]); // define the directive myApp.directive("fadeMe", ['$animate', function($animate) { return function(scope, element, attrs) { // watch the fade-me directive scope.$watch(attrs.fadeMe, function(newVal) { if (newVal) { $animate.addClass(element, "fade", function() { // this its the call back, wrap it in $apply to update the angular models scope.$apply(scope.flip()); }); } else { $animate.removeClass(element, "fade"); } }); } }]); // define the animate service myApp.animation(".fade", function() { return { addClass: function(element, className, callback) { element.animate({ opacity: 0 }, callback); }, removeClass: function(element, className, callback) { element.animate({ opacity: 1 }, callback); } } }); </script> </body> </html>
Wrap it up
- Import necessary scripts (jQuery for animate(), angularjs, angularjs-animate).
- Create the directive and define the animate service.
- Watch the fade value in the directive and provide a callback with $apply to update the angular model.
The key idea here is to use scope.$apply
to update the angular models, because I think the callback is just passed to jQuery as a normal callback function, and has nothing to do with angular. It is about the same issue when you want to use a jQuery plugin.