Home / Blog / Conditional View Change Animations in Angular JS

Conditional View Change Animations in Angular JS

By Sam Gluck, 11 Jan 2015

Suppose you have a number of views that share the same controller but have different routes and between these views you would like to suppress or manage the type of animation that occurs between them during a route change event. How do you achieve this? In Angular we have access to the run method, which is the perfect place to hook into its $routeChangeStart event. As a first step, let’s take a look at how we do this:

// *module definition and configuration stuff...*
.run(function($rootScope) {
    $rootScope.$on('$routeChangeStart', function(event, next, current) {
        // event => route change event
        // next => an object representing the next route a client is navigating to
        // current => an object representing the current page's route
    });
});

In this hook we can now inspect where the user is within the application, current, and where they are going, next. And as a side note, we could prevent the client from navigating onwards to next by using the familiar event#preventDefault method. Pretty cool, huh?

Before we continue, both next and current have a property $$route which is the object you will have assigned the route during its definition in $routeProvider. It contains some extra properties that are useful to us, in particular the originalPath property, which is a string representing the route’s pathname.

Next up, how should we decide when an animation occurs? We could do a switch on next‘s or current‘s $$route.originalPath property and conditionally apply styles as we please. Or we could use a bunch of if statements. But this wouldn’t look very pretty and is likely to hinder maintainability. So, seeing as within the $routeChangeStart event hook we can access a route’s original definition object we could add a property to this object that defines the behaviour of view animations when navigating away from the route. Let’s call it noAnimationTo, as in ‘do not animate when navigating to these routes from here’:

$routeProvider
    .when('/home', {
        templateUrl: templateUrl + '/home.html',
        controller: 'HomeController',
        noAnimationTo: ['/home/dashboard']
    })
    .when('/home/dashboard', {
        templateUrl: templateUrl + '/home-dashboard.html',
        controller: 'HomeController', // Same controller as /home route
        noAnimationTo: ['/home']
    });

From our /home route, if the user navigates to /home/dashboard, we can now see from our $routeProvider that an animation will not occur, similarly if the user navigates from /home/dashboard to /home. The value of noAnimationTo is an array so that should we create another route further down the line for which an animation should also not occur, we can simply add it is a new element and without further effort the animation will be suppressed.

Now the body of our $routeChangeStart event hook can observe the noAnimationTo property and notify the view through a variable on $rootScope whether or not to animate. Like so:

// *module definition and configuration stuff...*
.run(function($rootScope) {
    $rootScope.$on('$routeChangeStart', function(event, next, current) {
        if(!next || !current || !next.$$route || !current.$$route) {
            return;
        }

        $rootScope.animateViewChange = (current.$$route.noAnimationTo)
            ? !(current.$$route.noAnimationTo.indexOf(next.$$route.originalPath) > -1)
            : true;
    });
});

What’s going on here? We use a ternary operator to decide what the value of $rootScope.animateViewChange will be: if the current route does not have a noAnimationTo property then set animationViewChange to true (i.e. perform animation), otherwise if the current route’s noAnimationTo array contains the originalPath string of the next route then it will be false (i.e. do not perform animation). Then on our ng-view element we can use the ng-class directive to conditionally apply a class, ‘animate‘,  when $rootScope.animateViewChange is true:

<div class="main" ng-class="{ animate: animateViewChange }" ng-view></div>

In our CSS our .ng-enter/.ng-leave, etc. animation class will be attached to the animate class, like so:

.main {
    /* ng-view styles */
    transition: all 0.25s ease;
}

.main.animate.ng-enter {
    /* $next view enter animation styles */
}

.main.animate.ng-leave {
    /* $current view leave animation styles */
}

Although above we only demonstrated how to suppress animations on a route change, with some different logic in the $routeChangeStart event hook we could change $rootScope.animateViewChange to be a string which denotes a type of animation that should occur, defined by the next route. For example:

// Route provider:
$routeProvider
    .when('/home', {
        templateUrl: templateUrl + '/home.html',
        controller: 'HomeController',
        animationClassName: 'homeAnimation'
    });

// Inside $routeChangeStart event hook:
$rootScope.animateViewChange = next.$$route.animationClassName || 'defaultAnimation';

And then rather than using the ng-class directive, we simply use a binding expression to place the value of $rootScope.animateViewChange inside the ng-view element’s class attribute:

<div class="main {{ animateViewChange }}" ng-view></div>

– Sam

[Abi wrote this bit] We are always looking for new developers so if you’ve read this blog, found it helpful and are interested to find out more about what we do, get in touch, we are always up for a pint and a chat in the pub team@outlandish.com