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? Commented Jan 10, 2015 at 17:47
  • The latest ones bower installs, but I can install any other version if that's important Commented Jan 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. Commented Jan 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 Commented Jan 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. Commented Jan 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/

Sign up to request clarification or add additional context in comments.

4 Comments

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 it
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
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.
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/appear angular.github.io/angular-phonecat/step-12/app/#/phones htmlxprs.com/post/9/easy-angularjs-animations-using-animatecss
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.

Comments

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.

Comments

-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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.