5

I have a block with ng-repeat that is defined like this:

<div ng-show="isPageSelected(item.page)" class="block" ng-repeat="item in data"> ... </div> 

Currently I can switch between those blocks, by clicking certain elements. It works via ng-show="isPageSelected(item.page)" as you might have guessed. It all works fine, but they are switching instantly and I want to add an animation, a simple fade in/fade out will do.

So, the block that is no longer selected should fade out and when it's gone a new block should fade in. When I'm using ngAnimate they fade in and fade out simultaneously. I need the first block to disappear completely and be hidden with display: none; and when it's done the next block should appear and fade in. It's a rather straightforward task when using jQuery, but how do I do that elegantly with Angular.js?

I have a strong suspicion that Angular.js isn't exactly a good choice for a site with complex animations.

EDIT: To simplify my question, all I need to do is

  1. On a button click start an animation;
  2. When an animation has been finished, change model;
  3. Run another animation.

Since I need to change the model after an animation, it's probably not possible to do it via pure CSS. The only way I know of triggering animations on specific elements in angular is to create a directive, pass a scope variable into the directive, create watcher for that variable in the directive and then change the variable from the controller:

<div animation="page"></div> 

app.directive('animation', function(){ return { scope: { page: '=animation' }, link: function(scope, element){ scope.$watch('page', function(newVal){ ... }); } }; }); 

I guess it would work, but it seems really bloated to create a directive just for that. Also, how would I change $scope.page with this approach only when the animation has been finished? Add another scope variable just to trigger an animation and somehow change $scope.page when an animation has been finished? It's possible to do it with ngFx module, but the amount of code it takes is just ridiculous. At this point I think adding jQuery animations to the controller would be a prettier way to solve it.

EDIT: That's how it looks like with jQuery animations:

$scope.changePage = function(page) { $('.block').animate({opacity: 0}, 500, function(){ $scope.page.id = page; $scope.$apply(); $(this).animate({opacity: 1}, 500); }); }; 

It works fine and it's not quite as verbose as the way with directives, but I have to use CSS selectors and that's just feels very "unangular". Do you guys use something similar when dealing with animations?

EDIT: Somewhat similar approach using ngFx:

 <div ng-hide="switchPageAnimation" class="block fx-fade-normal fx-speed-300 fx-trigger"> 

In the controller:

 $scope.switchPageAnimation = false; $scope.changePage = function(page) { if($scope.page.id === page || $scope.switchPageAnimation) return; $scope.switchPageAnimation = true; $scope.$on('fade-normal:enter', function(){ $scope.page.id = page; $scope.switchPageAnimation = false; }); }; 

I'm not using CSS selectors, but still it looks awful. I have to define a scope variable for the animation and then check if the animation is already running. I feel like I am missing something really obvious.

5
  • 1
    what version of angular and angular-animate are you using?
    – scniro
    CommentedJan 10, 2015 at 17:47
  • The latest ones bower installs, but I can install any other version if that's importantCommentedJan 10, 2015 at 18:55
  • Angular's version is 1.3.8. I'm also using ngFx with version 1.0.5. Angular-animate has the same version as Angular, if I understand it correctly.CommentedJan 10, 2015 at 19:03
  • with angular 1.3 u have the $animate service docs.angularjs.org/api/ngAnimate/service/$animate there u can register promise callbacks when the animation is complete
    – micha
    CommentedJan 12, 2015 at 0:32
  • Okay, but how do I trigger it initially? If I put it inside a directive, I'll have to create a scope variable to trigger it, right? That's the whole problem. If I'm going to use CSS selectors, then I can just stick with jQuery. Please, provide an example, if I'm misunderstanding something.CommentedJan 12, 2015 at 12:31

4 Answers 4

7
+100

Maybe this will help you and it is not necessary in your case to wait for the animation to finish. If your pages have css position: absolute and are in a container with position: relative then they share the same place and are not shown one below the other while animation. With this setting you can crossfade or delay the show animation

transition-delay:

.container1{ position: relative; height:400px; } .block1{ position:absolute; } .block1.ng-hide-add-active { display: block!important; -webkit-transition: 2s linear all; transition: 2s linear all; } .block1.ng-hide-remove-active { display: block!important; -webkit-transition: 2s linear all; transition: 2s linear all; -webkit-transition-delay: 2s; transition-delay: 2s; } .block1.ng-hide { opacity: 0; } 

http://jsfiddle.net/ncrs4gz0/

Edit: If u use a filter in ng-repeat instead of ng-show to show a selected page like this

<div class="block1" ng-repeat="item in data | filter:isPageSelected"> 

then the pages are added and removed from the dom and angular add classes ng-enter , ng-enter-active and ng-leave ng-leave-active

but the animation can be defined similar see fiddle : http://jsfiddle.net/o944epzy/

4
  • Okay, that's nice, but it won't work if the pages were switched by using something like that: <div ng-repeat="video in videos | onlyPage:page" class="col-item">, i.e. they would be removed from the dom when page is changed. I'm using this approach in another place and still forced to use jQuery for itCommentedJan 15, 2015 at 10:48
  • than u have to use .col-item.ng-enter-active and .col-item.ng-leave-active -there are a lot of examples on google animating for ng-repeat
    – micha
    CommentedJan 15, 2015 at 20:03
  • Thank you, you are being very helpful! I assume there is no way to make it work if there is more than a single element in ng-repeat though, right? I mean, if we use position: absolute we can't put a few elements next to each other without specifying their exact position. Oh, well.CommentedJan 16, 2015 at 19:54
  • we only use this setting with position absolute in a relative container when we have page like transitions. when more than a single element is shown in ng-repeat than the elements have position: relative or even floating.Here a few examples nganimate.org/angularjs/ng-repeat/appearangular.github.io/angular-phonecat/step-12/app/#/phoneshtmlxprs.com/post/9/easy-angularjs-animations-using-animatecss
    – micha
    CommentedJan 18, 2015 at 12:03
2

Effectively you have to use CSS for showing and hiding elements from your ng-repeat; when you use ngAnimate, do not forget to inject it in your module.

.module('myModule', [ 'ngAnimate', ... 

Doing that when you will add/remove an element from you ng-repeat source of data. Angular will add and remove the class ng-enter, ng-leave. Here is a good tutorial about animation with ngAnimate.

In your own case you want to change page. I suggest you have too many items for displaying all at once.

You can declare in your scope two variable :

$scope.elemByPage = 5; $scope.page = 0; $scope.nbPages = ...; // I let you do the maths ;) 

and after in your template you can do simply this:

<div class="my-repeat-item" data-ng-repeat="item in data | pager:page:elemByPage"> {{item.xxx}} </div> 

This template will only show the needed items in function of the page number and number of elements per page. Pager is a simple filter and it does the trick

.filter('pager',[function(){ return function(items, page, nbElemByPage) { if(!nbElemByPage || nbElemByPage < 1) { return items; } var nbPages = Math.floor(items.length/nbElemByPage); if(nbPages<1) { return items; } var startIndex = page*nbElemByPage; return items.splice(startIndex, nbElemByPage); }; }]) 

Now you just have to use button that will allow you to browser your items

<button data-ng-click="page = page - 1"/>Prev page</button> <button data-ng-click="page = page - 1"/>Next page</button> 

To finish you want to add a fade in animation on new items so declare in your css these classes following the class of your items (here my-repeat-item)

.my-repeat-item.ng-enter { transition: 0.6s ease all; opacity:0; } .my-repeat-item.ng-enter.ng-enter-active { opacity:1; } 

You can do the same thing when a item is removed by replacing enter with leave.

Hope it will answer to your question.

    0

    Effectively you have to use CSS for showing and hiding elements from your ng-repeat; when you use ngAnimate, do not forget to inject it in your module.

      -1

      You can add your desired transition to the CSS class of the element to show/hide:

      animate-show.ng-hide-add.ng-hide-add-active, .animate-show.ng-hide-remove.ng-hide-remove-active { -webkit-transition: all linear 0.5s; transition: all linear 0.5s; } 

      See more here: https://docs.angularjs.org/api/ng/directive/ngShow#example

      In fact, using CSS is the 'right' way to do it even though it even though it might feel un-angular.

        Start asking to get answers

        Find the answer to your question by asking.

        Ask question

        Explore related questions

        See similar questions with these tags.