Modifying DOM (slideDown/slideUp) with AngularJS and jQuery
Asked Answered
S

2

20

I'm trying to implement a slideDown/slideUp animation with AngularJS. I can't use CSS3's transition (unfortunately) since the height is set to auto (and I don't want to use the max-height workaround), so I'm trying to use jQuery's slideToggle method.

Given the following markup:

<ul>
    <li ng-repeat="entry in data">
        <span>{{entry.title}}</span>
        <a ng-click="clicked($event)" href>more?</a>
        <p>{{entry.description}}</p>
    </li>
</ul>

I implemented the following method in my controller:

$scope.clicked = function($event) {
    var a = jQuery($event.target);
    var p = a.next();
    p.slideToggle();
};

FIDDLE

Even if it seems to work as expected, I understood that modifying DOM shall be done exclusively within directives.

After having read AngularJS' documentation (which I find a bit light IMHO), directives are still a bit vague to me, so could anyone tell me whether the following directive respects AngularJS's best pratices or not?

.directive('testDirective', [
function() {
    return {
        restrict: 'A',
        scope: {
            entry: '=testDirective'
        },
        template: '<span>{{entry.title}}</span> ' +
                  '<a ng-click="clicked($event)" href>more?</a>' +
                  '<p>{{entry.description}}</p>',
        link: function(scope, element) {
            var p = jQuery(element.find('p'));
            scope.clicked = function($event) {
                p.slideToggle();
            };
        }
    };
}])

FIDDLE

Could it be improved? Am I allowed to use jQuery within a directive? Does it respect the separation of concerns?

Sheaves answered 26/3, 2014 at 11:34 Comment(0)
E
42

Alternatively, you can use AngularJS's $animate:

.animation('.slide', function() {
    var NG_HIDE_CLASS = 'ng-hide';
    return {
        beforeAddClass: function(element, className, done) {
            if(className === NG_HIDE_CLASS) {
                element.slideUp(done); 
            }
        },
        removeClass: function(element, className, done) {
            if(className === NG_HIDE_CLASS) {
                element.hide().slideDown(done);
            }
        }
    }
});

Use ng-hide or ng-show to show or hide the description.

    <li ng-repeat="entry in data">
        <span>{{entry.title}}</span>
        <a ng-click="expand = !expand" href="#">more?</a>
        <p class="slide" ng-show="expand">{{entry.description}}</p>
    </li>

See JSFiddle

P.S. you must include jquery and angular-animate.js

Eadmund answered 26/3, 2014 at 15:52 Comment(13)
Interesting, thank you! Here could be another solution, what do you think about it?Sheaves
That is good too. Using ng-show is just a matter of personal preference.Eadmund
You also need to be using a full JQuery library for this to work. If you are just using the version of JQuery Lite that Angular includes (via angular.element) then .slideDown is not present.Oligochaete
Cool. I got this to work thanks to the jsFiddle. How would you closed (slideUp()) all the other classes prior to expanding the current one (to keep all the results compact)? I've tried jQuery(className).slideUp(done) in various places but with no success. Also using pure jQuery ($('.slide').slideUp();) just seems to break things :(Randell
Simplest way is to store the expanded entry in the parent scope. See this JSFiddleEadmund
Great answers, thanks. In case someone needs to have something expanded by default: JSFiddleOphiolatry
Sevenearths, are you saying that the slideUp() part of the process isn't animating? What's probably happening is ng-hide is taking it display: none and preventing the animation. Try .animation-class.ng-hide.ng-animate { display: block !important; } Hate going important, but jQuery is probably adding display: none to the element.Forwards
@Eadmund - This is simply amazing. But I just need one help. I implemented this on my project. What's happening is. I get all the items collapsed at once when I click on a button. Whereas in jQuery we would use the .closest() to find the nearest element in the container to toggle. But how do we do it Angular?Vivisection
I'm sorry if I'd confused you. I'm not looking for hideall button. In the fiddle you've provided only p tag gets expanded on the click of an a tag. Whereas in my project when I click on a tag all the elements gets toggled.Vivisection
Note that if you convert this code into Coffeescript, make sure you return undefined at the end of your beforeAddClass and removeClass functions. If you just blindly convert the above to CS, you'll probably inadvertently return the result of jQuery(element).slideUp(done) (because CS automatically returns the last line of your function, which will cause things to break in Angular. (I just wasted 15 minutes because of this!)Iand
If anyone is having trouble with stuttering animations, remember that min-height can do that to you. Ran into this issue on Angular Material. A modified plunker of how you could resolve this here: jsfiddle.net/d7Lqsh59/1Latricelatricia
Important! The answer is not complete or fully correct, because in some cases animation is not marked completed in angularjs (temp css classes e.g. ng-hide-animate not going). That leads to problem with binded elements (with ng-model). Such elements may have falsy ng-show expression and still stay showing up. Solution: add "else {done()}" after "if{}" blocks.Mersey
Loved it. Simple and clean. ThanksPippy
M
8

I want to show an example of how this is done with ng-if, since I was looking for a solution for hours running into multiple completely different approaches that work with ng-show only. You may use ng-if vs ng-show if say you need directives initialized only upon becoming visible.

To use jQuery slideDown / slideUp with ng-if, you can also use ngAnimate but using different hooks: enter and leave.

app.animation('.ng-slide-down', function() {
  return {
    enter: function(element, done) {
      element.hide().slideDown()
      return function(cancelled) {};
    },
    leave: function(element, done) { 
      element.slideUp();
    },
  };
});

<div class="ng-slide-down" ng-if="isVisible">
     I will slide down upon entering the DOM.
</div>

This was surprisingly difficult to accomplish, even trying various JS based approaches and CSS only approaches. Ultimately there is a time and place for jQuery's animations.

Mcmorris answered 9/12, 2014 at 20:6 Comment(2)
Nice solution, works for me, thanks; what did you intend to do with return function(cancelled) {}; ?Precontract
@Precontract that's a stub for behavior to be done if animation is canceled mid way. I'm not sure of the details but it would be in the docs.Micrometeorology

© 2022 - 2024 — McMap. All rights reserved.