Catalin Ciubotaru avatar

How to setup reusable animations with Angular

How to make your Angular apps prettier, more user-friendly, and overall more whimsical.

The Problem

Animations are always fun. CSS is this magical language that, on top of making things pretty 🎨, it also allows you to make things move. Now, while all this is nice and fun, Angular (arguably) makes this a bit more difficult. Mainly because if you use ngIf for example, the DOM element does not exist until the condition is met. Furthermore, when an element leaves the view, it completely disappears. You see, the way Angular(or any other FrontEnd framework for that matter) works, is by synchronising some sort of state with the DOM. It does this by monitoring what should be there, and what shouldn’t. This means, when you want to animate something getting into view, or out of view, you need to work a bit with Angular.

Good to know

This is not intended to be a full CSS Animations tutorial. I recommend reading this if you are keen to know more. Moreover, the CSS for JS by Josh Comeau is wonderful, if you want to get more familiar with CSS overall. This article also does not teach you Angular. It assumes you already know how to use it (at a basic level).

The Solution

There are a few parts to our solution:

  1. Anatomy of a CSS Animation
  2. Anatomy of an Angular Animation
  3. See it in action
  4. How to make these reusable

For this exercise, we'll animate something fading into view, and out of view.

Part 1 - Anatomy of a CSS Animation

To animate something with CSS, we need a couple of things. First, we require a selector for the element we want to animate. We usually do this by using a class name, something like this .fade. Nothing fancy.

Secondly, we need to tell this element what we’ll animate, for what duration, and using what kind of easing function. Might look something like this:

transition-duration: 0.2s;
transition-timing-function: ease-out;
transition-property: opacity;

If this doesn’t make a lot of sense, have a look at the article I posted above 👆.

Then, we need to setup an initial state for this element. Let’s say this element starts invisible. In this case, we might do something like this: opacity:0;.

Together with this, we also need to setup the end state. To make the difference between states, we typically attach a new class to it. Something like this: .fade.active. This one, in our case, would have the opacity property changed: opacity: 1;.

The complete picture looks something like this.

.fade {
opacity: 0;
transition-duration: 0.2s;
transition-timing-function: ease-out;
transition-property: opacity;
}

.fade.active {
opacity: 1;
}

Long story short, we need to:

  • identify the element we want to animate
  • setup initial state
  • setup end state
  • setup transition properties, like duration, timing etc.

Part 2 - Anatomy of an Angular Animation

Let’s see how the same animation might look in Angular. It all starts with states. An element can have different states during its life. For simplicity, in our case, the element has two states: visible and invisible. It’s visible when the object is part of the DOM. We want the opacity to be 1 for this situation. Otherwise, it’s invisible when the element is not part of the DOM. We’ll want to have opacity set to 0 for this situation. All of this is equivalent to saying the element has the .active class or not(using the example from Part 1).

The next concept is transition. An element can transition from one state to another. In our case, the element will transition from invisible to visible, and the other way around. We can represent these transitions like this: visible => invisible and invisible => visible. Since this is a very common transition for Angular animations, we’re provided with an alias for this kind of transition. We can simply use :enter and :leave.

The next two concepts are easier: style and animate. The style function is used to provide an object that dictates what the properties should look like for a specific state. In our case, when the element is visible, we’ll call it like the function like this: style({opacity: 0});. Similarly, when the element is invisible, we’ll do it this way: style({opacity: 0});. We’ll need to call this function only with the properties that change between states. Everything else stays the same.

The animate function is used to specify animation timing and the new style that should be applied. For our example, we’ll use something like this: animate('.2s ease-out', style({ opacity: 0 })). This tells Angular to animate the element for a duration of 0.2s, during which the opacity will go to 0.

Part 3 - See it in action

Plenty of concepts, I know. Let’s see if putting this together makes sense. Here’s what this animation would look like:

@Component({
selector: 'app-message',
animations: [
trigger('fadeIn', [
transition(':enter',
[style({ opacity: 0 }),
animate('.2s ease-out', style({ opacity: 1 }))
]),
]);
],
template: '<div *ngIf="isVisible" @fadeIn>Here is a message</div>'
})
export class MessageComponent {
@Input() isVisible = false;
}

Let’s unpack this 📦. We have a component that has isVisible as an input. This controls if the component should be visible or not. The template is nothing spectacular. It shows/hides a div, based on the value of isVisible. One small thing to note in the template: there’s a @fadeIn tag. The tag is used to apply a defined animation. It is basically the selector from above. This is how we identify WHAT should be animated.

Now, going to the new animations array. Here’s where the magic ✨ happens. We start with a. trigger function. This is used to tell Angular what animation to perform on which trigger. Then, we need to tell Angular which transition to animate. Using the alias mentioned above, we’re telling to animate the entrance into view, aka :enter, aka invisible state => visible state.

Finally, we’re passing the initial style(opacity: 0) and telling it to animate the end state by going to an opacity: 1 during 0.2s.

I know there’s a lot highlighted here. I really hope it makes sense.

Now, to make things more complicated, we can also add a fade-out animation. We want our element to animate the fade-in, but we also want it to animate the fade-out. The good news is that we can do this in the already defined trigger. Here’s the updated code:

@Component({
selector: 'app-message',
animations: [
trigger('fadeIn', [
transition(':enter',
[style({ opacity: 0 }),
animate('.2s ease-out', style({ opacity: 1 }))
]),
transition(':leave',
[animate('.2s ease-in', style({ opacity: 0 }))])
]);
],
template: '<div *ngIf="isVisible" @fadeIn>Here is a message</div>'
})
export class MessageComponent {
@Input() isVisible = false;
}

The only thing we did, is adding a new transition for :leave, and we gave that new transition an animation function that will go to opacity: 0 over 0.2s. Since this is a :leave animation, we don’t need to pass an initial state, just an end state; where the DOM element ends up.

Part 3 - Making all this reusable

Ok, all this is nice and smooth. What about reusing this? Let’s say we have an ErrorComponent and we want it to use the same animations. That’s a lot of copy/pasting. Well, we can declare our animation as an exported variable. Like this:

export const fadeInOutTrigger = trigger('fadeInOut', [
transition(':enter', [style({ opacity: 0 }), animate('.2s ease-out', style({ opacity: 1 }))]),
transition(':leave', [animate('.2s ease-in', style({ opacity: 0 }))])
]);

It’s the same piece of code that we saw above, in the MessageComponent. It just lives in a separate file, and can easily be imported anywhere. After doing this, our new ErrorComponent can look something like this:

@Component({
selector: 'app-error',
animations: [fadeInOutTrigger],
template: '<div *ngIf="isVisible" @fadeIn>Error time!</div>'
})
export class MessageComponent {
@Input() isVisible = false;
}

And with this, we have achieved…. REUSABLE ANIMATIONS WITH ANGULAR.

Time to pat yourself on the back. Good job. 👏

Congrats 🎉

You made it yet through another article. Thanks for spending the time reading this, and hope it provided some sort of value, since time is a limited resource. Use it wisely. 🙏

Want more?

If you want to know more about this, or something doesn't make any sense, please let me know.

Also, if you have questions, you know where to find me… On the internet!

Be kind to each other! 🧡

Over and out

My Twitter avatar
Catalin Ciubotaru 🚀

🤓 Learn how to setup reusable animations with Angular: https://catalincodes.com/posts/reusable-animations-with-angular #Angular #Animations #CSS

Apr 21, 2023
34 people are talking about this

Wanna read more?

Updates delivered to your inbox!

A periodic update about my life, recent blog posts, how-tos, and discoveries.

No spam - unsubscribe at any time!