Stop Angular Animation from happening on ng-show / ng-hide - angularjs

In my AngularJS application I'm using fontawesome for my loading spinners:
<i class="fa fa-spin fa-spinner" ng-show="loading"></i>
I'm also using AngularToaster for notification messages which has a dependency on ngAnimate. When I include ngAnimate in my AngularJS application, it messes up my loading spinners by animating them in a weird way. I want to stop this from happening but cant find a way to disable the animation on just these loaders (it would also stink to have to update every loader I have in my app).
Heres a plunkr showing my exact problem.
http://plnkr.co/edit/wVY5iSpUST52noIA2h5a

I think the best way to do this is to use the $animateProvider.classNameFilter which will allow you to filter items to animate or in this case not to animate. We'll do something like:
$animateProvider.classNameFilter(/^((?!(fa-spinner)).)*$/);
//$animateProvider.classNameFilter(/^((?!(fa-spinner|class2|class3)).)*$/);
Demo:
http://plnkr.co/edit/lbqviQ87MQoZWeXplax1?p=preview
Angular docs state:
Sets and/or returns the CSS class regular expression that is checked
when performing an animation. Upon bootstrap the classNameFilter value
is not set at all and will therefore enable $animate to attempt to
perform an animation on any element. When setting the classNameFilter
value, animations will only be performed on elements that successfully
match the filter expression. This in turn can boost performance for
low-powered devices as well as applications containing a lot of
structural operations.
As another answer per the comment with the no-animate directive, you could write an ng-show directive that will run at a higher priority and disable the animation. We will only do this if the element has the fa-spinner class.
problemApp.directive('ngShow', function($compile, $animate) {
return {
priority: 1000,
link: function(scope, element, attrs) {
if (element.hasClass('fa-spinner')) {
// we could add no-animate and $compile per
// http://stackoverflow.com/questions/23879654/angularjs-exclude-certain-elements-from-animations?rq=1
// or we can just include that no-animate directive's code here
$animate.enabled(false, element)
scope.$watch(function() {
$animate.enabled(false, element)
})
}
}
}
});
Demo: http://plnkr.co/edit/BYrhEompZAF5RKxU7ifJ?p=preview
Lastly, and similar to the above, we can use the no-animate directive if we want to make it a little more modular. In this case I'm naming the directive faSpin which you could do in the answer above. This is essentially the same only we are preserving the directive from the SO answer mentioned in the comment of the above codeset. If you only care about the fa-spin animation issues naming it this way works well, but if you have other ng-show animation problems you'd want to name it ngShow and add to the if clause.
problemApp.directive('noAnimate', ['$animate',
function($animate) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$animate.enabled(false, element)
scope.$watch(function() {
$animate.enabled(false, element)
})
}
};
}
])
problemApp.directive('faSpin', function($compile, $animate) {
return {
priority: 1000,
link: function(scope, element, attrs) {
if (element.hasClass('fa-spinner')) {
element.attr('no-animate', true);
$compile(element)(scope);
}
}
}
});
Demo: http://plnkr.co/edit/P3R74mUR27QUyxMFcyf4?p=preview

I had a similar problem where my icon would keep spinning until the animation finished, even after the $scope variable turned off. What worked for me was to wrap the <i> fa-icon element in a span.
<span ng-if="loading"><i class="fa fa-refresh fa-spin"></i></span>
Try it!

I've found an easier way.
<i class="fa fa-spinner" ng-show="loading" ng-class="{'fa-spin' : loading}"></i>
Forked plunker: http://plnkr.co/edit/mCsw5wBL1bsLYB7dCtQF
I did run into another small issue as a result of doing this where the icon would appear out of position if it spun for more than 2 seconds, but that was caused by the 'ng-hide-add-active' class, so I just added in my css:
.fa-spinner.ng-hide-add-active {
display: none !important;
}
and that took care of it.
EDIT: Nico's solution is a slightly cleaner version of this, so I'd consider using his.

angular.module('myCoolAppThatIsAwesomeAndGreat')
.config(function($animateProvider) {
// ignore animations for any element with class `ng-animate-disabled`
$animateProvider.classNameFilter(/^((?!(ng-animate-disabled)).)*$/);
});
Then you can just add the class ng-animate-disabled to any element you want.
<button><i class="fa fa-spinner ng-animate-disabled" ng-show="somethingTrue"></i></button>

Updating James Fiala Answer.
<i class="fa fa-spinner fa-spin" ng-show="loading"></i>
You don't need the ng-class as mentioned in #James Fiala Answer. But you should have fa-spin as one of the class.
Add style
.fa-spinner.ng-hide-add-active {
display: none !important;
}

Related

AngularJS parent div directive wait for children div stacked directives to complete

I have a portion of html that is like this
<div my-custom-security-directive>
<button ng-if={{some constraints}} ng-click={{logic}}>cancel</button>
<button ng-disabled={{some logic}} ng-click={{some logic}}>submit<button>
</div>
My custom security directive does dom manipulation to the buttons to show/hide them via css. The issue I am having is with timing and perhaps directive precedence? When all the code is finished executing I only see the submit button and not the cancel button. I believe this is because of the ng-if and I attempted to set the priority of my custom directive to a negative number to run last but I think that is only for stacked directives on the same element? I only have a link function defined in my directive which my understanding is that is the same as a post link function and should run once the children complete? Ultimately, my goal is to run my directive 'last' so that I can show both buttons if all the logic in the directive passes.
The shell of my directive :
angular.module('myModule')
.directive("myCustomSecurityDirective", function(a,b) {
//
return {
restrict: "A",
priority: -1,
link: function(scope, element, attrs, ctrl) {
//custom security role/perm logic using injected services a&b etc
if (userHasPermission ) {
element.find('input, textarea, button').addClass('my-show-css');
}
}
};
});
I did recently /today put that priority on the directive but I don't think it does anything in this particular scenario.
Even if my-custom-security-directive is able to attach a CSS class to the button and hide or show it, the button has its own ng-if condition. This means it's possible that the button could be destroyed and recreated later, and it wouldn't have the CSS class anymore. If the button uses ng-show instead of ng-if you may have more control, since the button would become hidden but remain in the DOM.
But I think my-custom-security-directive might want to have more control. You can use transclusion so that my-custom-security-directive acts as a container for each set of elements which should be destroyed or created based on userHasPermission.
Directive:
.directive('myCustomSecurityDirective', function () {
return {
transclude: true,
link: function (scope) {
scope.userHasPermission = true;
},
template: '<div ng-if="userHasPermission" ng-transclude></div>'
}
});
HTML:
<div my-custom-security-directive>
<button ng-if="...">cancel</button>
<button ng-disabled="...">submit</button>
</div>

AngularJs: disable anchor / ngif negated

I have this directive (hidden some sensitive values):
modApp.directive("dir1", [
function () {
return {
scope: {
"results": "=",
"allowView": "#",
"allowPrint": "#",
},
templateUrl: "myurl",
compile: function (elem, attrs) {
// some code
},
};
}]);
and the template snippet is:
<a ng-href="#/MyUrl/{{item.code}}" target="_blank">
<img class="img-thumbnail" ng-src="{{myUrlToImg}}">
</a>
So my issue is that I need to disable the anchor under certain security conditions.
Therefore when it is allowed, it is ok to show the anchor and the image.
But when it is not, it should only show the image.
The easy way would be to simply define something like:
<a ng-show="{{allowView}}"><img></a>
<img ng-hide="{{allowView}}"><img>
The problem that two requests to the image are generated, where it should only be one.
The ideal would be to have something like:
<a disabled></a>
I found a aDisabled directive here in SO, but it didn't work for me.
Also there is ng-if but I couldn't make it work with the negated value of allowView.
Any suggestions?
Plunker: http://plnkr.co/edit/UJgQsaDYaxErP5A2NZy2
This plunker does not still use the allowView scope variable.
Note that in html, I'm setting a hardcoded true/false, it seems that this also has an impact, but not sure why...

Angular directive to swap ng-click function

I have an app where a person can build lists of 'favorites.' On the page for a particular item there is a button that you can click to add the item to your list of favorites.
What I would like to happen is: Click the button, item is added to favorites, now the button is a different color and if you were to click it again it would remove the item from your favorites.
I think it would be best to build a directive to handle this but I am completely lost.
In my html I have:
<md-button class="md-icon-button" aria-label="Favorite" ng-click="addFavorite(item.id)">
<md-icon md-svg-icon="heart"></md-icon></md-button>
My addFavorite and deleteFavorite functions work correctly, but I can't figure out how to toggle which one happens and how to update that after the request fires.
So lets address a few different things.
Most likely this is going to be a div styled to look like a button to achieve the effect you want. You could go the easy route and use http://getbootstrap.com/css/?#buttons or something else. Styling buttons is just not going to be the best cross browser support.
You could do this entirely with ng-class in a directive, some example code below. Assuming you are using bootstrap. This just becomes what class do you want to change it to kinda thing. Below I have used an icon but the principal is the same.
Directive
.directive('toggleClass', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('click', function() {
if(element.attr("class") == "glyphicon glyphicon-pencil") {
element.removeClass("glyphicon glyphicon-pencil");
element.addClass(attrs.toggleClass);
addFavorite(element.id);
} else {
element.removeClass("glyphicon glyphicon-ok");
element.addClass("glyphicon glyphicon-pencil");
removeFavorite(element.id);
}
});
}
};
});
Template
<i class="glyphicon glyphicon-pencil" toggle-class="glyphicon glyphicon-ok"></i>

Running code after an AngularJS animation has completed

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?
#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
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 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

parent directive erasing child ng-repeat

I am learning angularjs through the process of taking an existing site that was built primarily with JQuery and trying to "angularize" it. I am having trouble reproducing the same functionality in angular.
Please see the following plunker.
http://plnkr.co/edit/n4cbcRviuzNsieVvr4Im?p=preview
I have a ul element with an angularjs directive called "scroller" as seen below.
<ul class="dropdown-menu-list scroller" scroller style="height: 250px">
<li data-ng-repeat="n in notifications">
<a href="#">
<span class="label label-success"><i class="icon-plus"></i></span>
{{n.summary}}
<span class="time">{{n.time}}</span>
</a>
</li>
</ul>
The scroller directive looks like this:
.directive('scroller', function () {
return {
priority: 0,
restrict: 'A',
scope: {
done: '&',
progress: '&'
},
link: function (scope, element, attrs) {
$('.scroller').each(function () {
var height;
if ($(this).attr("data-height")) {
height = $(this).attr("data-height");
} else {
height = $(this).css('height');
}
$(this).slimScroll({
size: '7px',
color: '#a1b2bd',
height: height,
disableFadeOut: true
});
});
}
};
What i want to happen is that the ng-repeat executes on the notifications array in the controller, producing a collection of li elements that exceed 250px therefore a slimscrollbar would be added. What actually happens is the result of the ng-repeat is not included in the final DOM. I believe the call in the parent scroller directive of $(this).slimScroll() is called after the ng-repeat executes and replaces the DOM. If i remove the scroller attribute, the li elements show up.
I am sure there is a strategy for this and am hoping the community can educate me on a better approach or alternate approach. thoughts? again the plunker is here.
http://plnkr.co/edit/n4cbcRviuzNsieVvr4Im?p=preview
Thanks,
Dan
The issue is actually your directive scope. You are using an explicit object as the scope, which means you are isolating the scope, which means the directive scope isn't inheriting from its parent anymore. So notifications from the parent controller is no longer reachable from the directive scope (and therefore any elements inside of its element).
If you remove this from your directive it should work:
scope: {
done: '&',
progress: '&'
}
I notice that you aren't using those attributes anyway so it shouldn't break any other functionality.
Look at the API docs http://docs.angularjs.org/guide/directive and look for isolate scope for more details.
An alternative to what you're trying to do would just be something like this
scope.$watch(attr.done, function(val) { //do something when the value changes })
Since I don't know your use case I can't say what the best solution would be.

Resources