25

I have an element whose visibility is toggled by ng-show. I'm also using CSS animations - the automatic ones from ng-animate - on this element to animate its entry.

The element will either contain an image or a video.

In the case that the element contains a video, I want to play it, but I don't want to play the video until it's finished animating in.

As such, I was wondering if there's an easy way to bind a callback to the end of a CSS animation in AngularJS?

The docs reference a doneCallback, but I can't see a way to specify it...

One workaround(?) I have thought of is $watching element.hasClass("ng-hide-add-active") and waiting for it to fire with (true, false), implying it's just been removed..

Is there a nicer way?

Ed_
  • 18,798
  • 8
  • 45
  • 71
  • you specify amount of seconds in css animations, set same amount in $timeout? – YOU Jan 04 '14 at 13:32
  • 2
    @YOU : I could do but that's even less neat in my opinion - the two are completely decoupled from eachother. – Ed_ Jan 04 '14 at 13:45
  • 1
    btw, there is some question regarding css animation callbacks - http://stackoverflow.com/q/6186454 , http://stackoverflow.com/q/9255279 , http://stackoverflow.com/q/2087510 – YOU Jan 04 '14 at 14:53
  • 2
    Thanks but this question is more about angularjs than generic css animations. – Ed_ Jan 04 '14 at 15:05
  • What you see in the docs is meant for directives. And I strongly recommend to implement one, as it's the only clean solution IMHO. – a better oliver Jan 04 '14 at 16:25
  • @zeroflagL I see - the element I'm animating in & out is a directive already? Is that what you mean? Or should I implement some kind of custom animation directive? – Ed_ Jan 04 '14 at 16:40
  • 1
    Instead of ng-show you use your own directive. Or if you already have one then use it for your animation as well and remove ng-show. – a better oliver Jan 04 '14 at 16:55
  • Thanks, I'll look into doing this. – Ed_ Jan 04 '14 at 16:57

3 Answers3

26

As @zeroflagL has suggested, a custom directive to replace ngShow is probably the way to go. You can use & to pass callbacks into the directive, which can be called after the animations have finished. For consistency, the animations are done by adding and removing the ng-hide class, which is the same method used by the usual ngShow directive:

app.directive('myShow', function($animate) {
  return {
    scope: {
      'myShow': '=',
      'afterShow': '&',
      'afterHide': '&'
    },
    link: function(scope, element) {
      scope.$watch('myShow', function(show, oldShow) {
        if (show) {
          $animate.removeClass(element, 'ng-hide', scope.afterShow);
        }
        if (!show) {
          $animate.addClass(element, 'ng-hide', scope.afterHide);
        }
      });
    }
  }
})

Example use of this listening to a scope variable show would be:

<div my-show="show" after-hide="afterHide()" after-show="afterShow()">...</div>

Because this is adding/removing the ng-hide class, the points about animating from the docs about ngShow are still valid, and you need to add display: block !important to the CSS.

You can see an example of this in action at this Plunker.

Michal Charemza
  • 25,940
  • 14
  • 98
  • 165
  • 1
    I around with this on Plunker and works great - however if I change the angular to version 1.3 then the callback is no longer fired. It looks like the addClass and removeClass functions no longer take a callback? – L. Desjardins Jan 19 '15 at 23:29
  • 2
    Nevermind, it looks like you have to use .then(function() {}); for version 1.3 – L. Desjardins Jan 19 '15 at 23:36
  • Also, if you're going to be using css animations, be sure to add `{tempClasses: 'ng-hide-animate'}` as the third argument after `element` and `'ng-hide'` before calling `.then` as mentioned in the above comment. – Matt Waldron Sep 22 '15 at 04:20
26

@michael-charemza answer worked great for me. If you are using Angular 1.3 they changed the promise a little. I got stuck on this for a little bit but here is the change that got it to work:

if (show) {
  $animate.removeClass(element, 'ng-hide').then(scope.afterShow);
}
if (!show) {
  $animate.addClass(element, 'ng-hide').then(scope.afterHide);
}

Plunker: Code Example

Lereveme
  • 1,614
  • 2
  • 15
  • 18
1

@michal-charemza solution works great, but the directive creates an isolated scope, so in some cases it cannot be a direct replacement for the default ng-show directive.

I have modified it a bit, so that it does not create any new scopes and can be used interchangeably with the ng-show directive.

app.directive('myShow', function($animate) {
  return {
    link: function(scope, element, attrs) {
      scope.$watch(attrs['myShow'], function(show, oldShow) {
        if (show) {
          $animate.removeClass(element, 'ng-hide').then(function(){
            scope.$apply(attrs['myAfterShow']);
          });
        } else {
          $animate.addClass(element, 'ng-hide').then(function(){
            scope.$apply(attrs['myAfterHide']);
          });
        }
      });
    }
  }
})

Usage:

<div my-show="show" my-after-hide="afterHide()" my-after-show="afterShow()">...</div>

Plunker

m.bemowski
  • 632
  • 6
  • 5