Search code examples
javascriptangularjsanimationng-animate

Conditionally animating ng-view transitions


I am trying to apply animations to ng-view (routing) depending of the views involved.

For example, from View1 to View2 I need the View1 leaving through the left side and View1 entering from the right side. Otherwise, from View2 to View1 I need View2 leaving through the right side and View1 entering from the left side.

But I have also situations where I need apply different animations to both views, for example, View1 leaving fading out and View2 entering scaling up.

What I am doing is using a scope associated variable as class in the ng-view:

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

This variable is set in each route change with something like this in each controller:

$scope.transition=Global.transition;

$rootScope.$on("$routeChangeStart",function (event, current, previous) {
   // Here I get the leaving view and the entering view and the kind of transition is selected
   ...
   $scope.transition=selectedLeavingTransition;  // Set the transition for the leaving view
   Global.transition=selectedEnteringTransition; // Set the transition for the entering view
});

Global is a service to set the transition variable for the entering scope from the leaving scope.

This way, when a route change is detected, the current ng-view is set with the class associated to selectedLeavingTransition, and the entering ng-view is set with the class associated to selectedEnteringTransition.

For example, if the route change was from View1 to View2 the ng-views during the animation could be:

<div ng-view class="fadeOut ng-animate ng-leave ng-leave-active"></div>
<div ng-view class="scaleUp ng-animate ng-enter ng-enter-active"></div>

The CSS in this case could be:

fadeOut.ng-leave {animation:1s fadeOut;}
scaleUp.ng-enter {animation:1s scaleUp;}

Though it works, I am wondering if there is a simpler way to do it as it seems a little mess.


Solution

  • An alternative solution that doesn't require much code is to define your animations on your routes:

    $routeProvider.when('/view1', {
      templateUrl: 'view1.html',
      controller: 'View1Controller',
      animations: {
        enter: 'enter-left',
        leave: 'leave-left'
      }
    });
    

    Then use a directive to retrieve the current route's animations and add them to the element:

    app.directive('viewAnimations', function ($route) {
      return {
        restrict: 'A',
        link: function (scope, element) {
          var animations = $route.current.animations;
          if (!animations) return;
    
          if (animations.enter) element.addClass(animations.enter);
          if (animations.leave) element.addClass(animations.leave);
        }
      };
    });
    

    And put the directive on the element that contains the ngView directive:

    <body ng-view view-animations></body>
    

    Demo: http://plnkr.co/edit/Y3ExDyiPIJwvVKO4njBT?p=preview

    Edit: New solution.

    To set animations during run-time I would use a service just like you are doing, but a directive to apply them.

    Very basic example of service:

    app.factory('viewAnimationsService', function ($rootScope) {
    
      var enterAnimation;
    
      var getEnterAnimation = function () {
        return enterAnimation;
      };
    
      var setEnterAnimation = function (animation) {
        enterAnimation = animation;
      };
    
      var setLeaveAnimation = function (animation) {
        $rootScope.$emit('event:newLeaveAnimation', animation);
      };
    
      return {
        getEnterAnimation: getEnterAnimation,
        setEnterAnimation: setEnterAnimation,
        setLeaveAnimation: setLeaveAnimation
      };
    });
    

    And the directive:

    app.directive('viewAnimations', function (viewAnimationsService, $rootScope) {
      return {
        restrict: 'A',
        link: function (scope, element) {
    
          var previousEnter, previousLeave;
    
          var enterAnimation = viewAnimationsService.getEnterAnimation();
          if (enterAnimation) {
            if (previousEnter) element.removeClass(previousEnter);
            previousEnter = enterAnimation;
            element.addClass(enterAnimation);
          }
    
          $rootScope.$on('event:newLeaveAnimation', function (event, leaveAnimation) {
            if (previousLeave) element.removeClass(previousLeave);
            previousLeave = leaveAnimation;
            element.addClass(leaveAnimation);
          });
        }
      };
    });
    

    Demo: http://plnkr.co/edit/DuQXaN2eYgtZ725Zqzeu?p=preview