1

I can't figure out why the following animation doesn't work as it should:

app.directive('openMenu', ['$animate', '$timeout', function($animate, $timeout) {
    return {
        link: function(scope, elem) {
            elem.bind('click', function() {

                if(elem.is(':animated'))
                    return;

                $timeout(function() {
                    $animate.addClass(elem, 'see');
                }, 0);

            });
        }
    }
}]);

And in this one the animation doesn't work at all (and class is not added either):

app.directive('openMenu', ['$animate', function($animate) {
    return {
        link: function(scope, elem) {
            elem.bind('click', function() {

                if(elem.is(':animated'))
                    return;

                $animate.addClass(elem, 'see');

            });
        }
    }
}]);

In the second code snippet I only removed $timeout, which is 0, I tried to use self-firing functions to check - animating works only when I am using timeouts. Can someone explain me why?

middle {
    margin-left: 0;
}
middle.see {
    margin-left: 270px;
}
.middle.see-add {
    -webkit-transition: margin-left 300ms;
    -moz-transition: margin-left 300ms;
    -ms-transition: margin-left 300ms;
    -o-transition: margin-left 300ms;
    transition: margin-left 300ms;
    margin-left: 0;
}
.middle.see-add.see-add-active {
    margin-left: 270px;
}

Here is the markup:

<div class="middle" open-menu></div>

3 Answers 3

2

Since your directive uses jQuery and jQuery modify the DOM, we need to tell angular about it. To do so, you need to do $scope.$apply but you would run into the error : "digest already in progress".

The code executed inside the $timeout guarantee that your code will be safely executed on the next digest cycle.

0 is the default value, you don't even need to specify it. You could simply write :

 $timeout(function() {
    $animate.addClass(elem, 'see');
 });

The $timeout service is simply a convenient service equivalent to :

var timeout = setInterval(function(){
    // do stuff
    $scope.$apply();
}, 0); 

You can find extensive information about the digest mechanism in the official documentation : https://docs.angularjs.org/error/$rootScope/inprog

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

3 Comments

No ` "digest in progress"` error should occur or the version without $timeout should have worked?
Sorry for my poor phrasing. What I meant is that the use of $scope.$apply would have triggered the error "digest already in progress" and that is why he should use $timeout to safely call $scope.$apply
Yes, I understood that, but I think if that would be the case you wouldn't need any $timeout or $apply callbacks.
2

Your problem is that you happen to be out of the Angular life-cycle. You are using .bind so Angular will not be notified whenever something has changed. The $timeout works because it's a one of the wrappers to notify Angular about an outside change. You could also use $apply. Both, the $timeout and $apply take a callback which will be executed. After the callback is finished Angular starts a digest beginning at the rootScope. Now every binding will be updated and everything should work properly.

scope.$apply(function() {
    $animate.addClass(elem, 'see');
});

Comments

1

I think it's because elem.bind('click') is a jQuery function, which is running outside of the angular digest phase.

Probably this would work as well instead of using $timeout:

scope.$apply(function() {
     $animate.addClass(elem, 'see');
})'

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.