When an Angular app refreshes the DOM, the default experience is jarring because Angular dumps the elements into the view with no transition.
The good news is Angular comes with great support for animations. The bad news is, it might not be exactly what you expected. Angular doesn't do animations for you, instead it provides events you can hook into with your own, custom animations.
So what's a developer to do?
When you add animations to your Angular application, you follow this pattern, but in a very Angular-y way that completely decouples your animation code from your directive code.
That might sound a bit weird to you right now, but the upside is you can create your own custom directives and then let end users of those directives define their own animations.
It's easier to understand how all the pieces fit together if you build and animate a simple custom directive you write yourself. Then you can circle back and understand exactly what's happening in the built-in directives.
Here is a simple directive for hiding an element with no support for animations:
The myHide directive watches an expression, (in this case the value of 'awesome'), and when the expression evaluates as truthy, adds a class to the element and removes it when it's false. The class sets display to none, so the myHide element is hidden when the expression is truthy.
This works, but there is no transition, it just pops in and out of existence.
The animation for myHide will fade the opacity of the element from 1 to 0, (and back when the state is toggled). At the end of the animation, display should be set to none.
And this presents an interesting problem, because you can't set display to none until the end of the animation — otherwise the whole animation is running while the element isn't visible. Oops. So you will need one CSS class to represent the transition/animation and another CSS class to set display to none after everything is done.
So the .hide-add-start class adds the transition and the final value, and then after the transition is done the .hide class is added which sets display to none.
To animate when the .hide class is removed, the first step is to set the opacity to 0. What would happen if you removed the hide class without setting the opacity to 0? The element would just pop in with no animation at all.
In order to create a transition from an opacity of 0 to 1, you need another class to define the end state. So one class will define the start state plus transition/animation and another will define the end state to animate to.
The line between removing .hide and adding .hide-remove-active causes a reflow. Without it, the browser doesn't apply the transition and the element just pops in.
The $animate service has methods for adding/removing classes and elements. When you use these methods in your directive, Angular automatically adds and removes classes to the element you are animating.
It adds and removes classes at correct times so you can define start and end states. Not only that, it reads the times from your CSS, so you define the timing in one place.
You can inject the $animate service without loading ngAnimate and everything will still work, it just won't trigger your animations. This is great because you can now create custom directives that will work even if animations aren't defined or used.
With $animate, the new version of the myHide directive now looks like this.
The CSS is going to be slightly different. In addition to the actual class you are adding to your element, addClass and removeClass add two additional classes: one for the animation and the starting styles and one more for the end styles. Both of these extra classes are removed at the end.
The CSS classes added follow a naming convention. So the class you are adding in this case is "hide" so $animate will add a "hide-add" class where you should define the animation and the starting styles and then a "hide-add-active" class which will contain any end styles.
Here is a screenshot of the docs which explains exactly what extra classes are created, the naming convention, and when each of the classes is added.
With that in mind, the CSS looks like:
The "hide-add" class sets display to "block" because the "hide" class is added at the same time and it sets the display to "none". Not what we want.
Even though this directive is only adding a class, $animate also supports methods for other operations on the DOM which will also add/remove CSS classes for you so you can animate basically anything in your Angular app.
Most of the built-in directives use $animate to do DOM manipulation, so that means you can animate those as well. The list of built-in directives that use $animate and what they support is here.
You can see, in the directive you use the $animate service the same way you use it for CSS animations. There's no difference.
Below the directive is the animation. The animation is named using a simple, single CSS class selector. Any element that uses this animation must have this class, otherwise it won't be animated.
The object returned by the animation call defines two properties, addClass and removeClass. These are defined because the directive is using addClass and removeClass. If the directive was removing or adding elements instead, you would define the 'leave' or 'enter' properties.
Most of the built-in directives use $animate just like the myHide directive. Just look at the code for ngHide:
Look familiar? That's because it's almost exactly the same as the myHide directive you've been looking at this whole time. There are a couple differences, mainly ngHide uses a ternary operator instead of an if/else to determine calling addClass or removeClass.
The Angular learning curve is a doozy. It's worth it in the end, but Angular is filled with a more than a few strange, new concepts and the end result can sometimes look downright magical.
All frameworks are opinionated, Angular is no exception. The problem is with Angular you can start creating apps that work easily, but before you know it you're running into problems that are difficult to troubleshoot and debug.
When you don't do things the "Angular way", you are swimming against the current. But how do you figure out the right way to do something in Angular?
Drop your email in the form below and I'll send you a few sample lessons on key Angular concepts like Dependency Injection and Directives. The lessons are all from the Angular Escape Plan, Angular training for busy, experienced web developers like you.