Search code examples
javascriptangularjsangularjs-directiveangularjs-animationng-switch

How to make Angular ng-switch animate(enter/leave) in more than one way?


I write an angular directive called 'cardViewer', which can show images inside it with animation.

When you press "prev" button, image slides left. When you press "next" button, image slides right.

I try to do this with ng-switch, which only supports .ng-enter and .ng-leave animation class. But I need two ways to enter(enter from left and right), two ways to leave(leave to left and right).

So I try ng-class to solve this problem. I hope it can add toLeft class before it switch, so it can apply specific css animation.

But it seems not working properly. When I press "next" button twice, it works fine. but when I press "next", then press "prev", new image enter in right direction, but old image leave in wrong direction.

My directive template:

<h1>hahaha</h1>
  <div>
    <button ng-click='prev()'>prev</button>
    <button ng-click='next()'>next</button>
  </div>
<div>current Card Index: {{displayCard}}</div>

<div class="card-container" ng-switch="displayCard">
<img class="card"
   src="http://i.imgur.com/EJRdIcf.jpg" ng-switch-when="1" 
   ng-class="{'toLeft': toLeft, 'toRight': toRight}"/>
<img class="card"
   src="http://i.imgur.com/StaoX5y.jpg" ng-switch-when="2" 
   ng-class="{'toLeft': toLeft, 'toRight': toRight}"/>
<img class="card"
   src="http://i.imgur.com/eNcDvLE.jpg" ng-switch-when="3" 
   ng-class="{'toLeft': toLeft, 'toRight': toRight}"/>
</div>

directive:

angular.module('app', ['ngAnimate'])
  .directive('cardViewer', function() {

  return {
    templateUrl: 'cardViewer.html',
    link: function(scope, element, attr) {
      scope.toLeft = false;
      scope.toRight = false;

      scope.displayCard = 1;

      //when press prev, card slide to left
      scope.prev = function() {
        scope.toLeft = true;
        scope.toRight = false;
        if (scope.displayCard == 1) {
          scope.displayCard = 3
        } else {
          scope.displayCard -= 1;
        }
      };

      //when press prev, card slide to right
      scope.next = function() {
        scope.toLeft = false;
        scope.toRight = true;

        if (scope.displayCard == 3) {
          scope.displayCard = 1
        } else {
          scope.displayCard += 1;
        }
      };
    }
  }
});

css:

.card-container {
  position: relative;
  height: 500px;
  overflow: hidden;
}
.card {
  width: 100%;
  position: absolute;
}

.card.ng-animate {
  transition: 1s linear all;
}

.card.ng-enter.toLeft {
  left: 100%;
}
.card.ng-enter-active.toLeft {
  left: 0;
}
.card.ng-leave.toLeft {
  left: 0;
}
.card.ng-leave-active.toLeft {
  left: -100%;
}

.card.ng-enter.toRight {
  left: -100%;
}

.card.ng-enter-active.toRight {
  left: 0;
}

.card.ng-leave.toRight {
  left: 0;
}

.card.ng-leave-active.toRight {
  left: 100%;
}

Here is my plunker: cardViewer

What's wrong with my code? What's the right way to make ng-switch enter/leave in more than one way?


Solution

  • The problem is that ngSwitch is destroying the scope of the current image before ngClass has a chance to execute and change that image's class from toRight to toLeft (or vice versa) before the animation starts.

    ngSwitch creates a new scope and executes at priority level 1200, while ngClass executes at priority level 0.

    To make it work, you just need to update your next and prev methods so they set the displayCard property in a $timeout, after toLeft and toRight are set, so ngClass has an opportunity to act on the new toLeft/toRight settings before ngSwitch destroys that scope.

    So your next method, for example, would look like this:

    scope.next = function() {
      scope.toLeft = false;
      scope.toRight = true;
    
      // need to do this in a $timeout so ngClass has 
      // chance to update the current image's class based 
      // on the new toLeft and toRight settings before
      // ngSwitch acts on the new displayCard setting
      // and destroys the current images's scope.
      $timeout(function () {
        if (scope.displayCard == 3) {
          scope.displayCard = 1
        } else {
          scope.displayCard += 1;
        }
      }, 0);
    };
    

    Here's a fork your plunk showing it working. Open the console to see log messages for when ngClass adds your classes and ngSwitch destroys and creates the scopes.

    I left your original "next" and "prev" buttons there and just added "next (fixed)" and "prev (fixed)" buttons so you can compare the log messages and see how, using the original buttons, ngSwitch destroys the scope before ngClass executes, while in the fixed versions ngClass executes before ngSwitch destroys the scope.