Getting Started With Animations For Angular: Fade + FadeSlide

Luis Ramirez Jr
Luis Ramirez Jr
hero image

Do you want to improve the user experience for your angular app?

To hook your user's attention, improve their understanding of how things work, and provide context to elements?

Well, good news. You can achieve all this and more, simply by adding in animations.

They don't even need to be anything too fancy. All you need are simple animations to help the users get contextual clues or feedback on their actions, and in today's article, we're going to walk you through 2 angular animation examples and how to implement them.

So let's get started...

CSS or Javascript packages for angular animations?💡

So what method should we be using to add these animations in our app?

Well, the most common approach is to use CSS transitions but the thing is, traditional CSS isn't always enough.

Traditional CSS is not enough

Sure, it does the job pretty darn well, but it can be quite difficult to use for more complex cases, such as animating elements that must be toggled from the document.

Make angular transitions easier

Luckily, Angular has a package called @angular/animations that allows us to leverage JavaScript instead for performing animations in an app.

And I know what you might be thinking: "But wait... can Javascript give us the performance we need?"

It sure can so let me explain...

The JavaScript animation myth

For the longest time, CSS was the most performant option for adding animations on the web and JavaScript couldn't compete.

However, most browsers have recently introduced the Web Animations API for performing animations with JavaScript.

What does this mean for Angular Developers?

Well under the hood, the Web Animations API uses the same engine as the CSS counterpart. And because Angular uses the same Web Animations API for handling animations, this means that we can expect the same performance as we would with plain CSS but without the drawbacks.

Sounds good?

Alright, so let's walk you through how to use this package.

Step #1. Installing the Angular animations package

Let's start by adding animations to our project.

As with any package, a module must be imported into our application to begin using its features. Animations can be imported by importing the BrowserAnimationsModule from the @angular/platform-browser/animations package.

As a side note, I've prepared a project that you can check out from this GitHub repo. After downloading this project, you will need to install the dependencies with the npm i command and start the server with the npm run start command.

For this lesson, we're importing this module into the AppModule, and you can import this like so:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [BrowserAnimationsModule]
})
export class AppModule { }

Next, in the app.component.ts file, we can start importing a series of functions from the @angular/animations package to start performing animations.

import {
 trigger, state, style, animate, transition, query, group
} from '@angular/animations';

If you're following along and implementing as you read, you might be freaking out right now because we just added a lot of functions, but don't worry because we'll go through them throughout this post.

For now we're just importing them ahead of time, so we don't have to revisit the import statement later.

Step #2. Define your basic animation

The first thing to work on for any animation, regardless of the complexity, is to begin defining your animations from within a component's configuration.

Inside the @Component decorator, you can add the animations property to define your animations.

If you need a refresher on decorators, check out the Writing Decorators lecture from the Complete Angular Developer course.

@Component({
  animations: [
    trigger('fade', [])
  ]
})

This property is an array of animations that can be applied to the elements within the respective component's template, and a new animation can be registered by using the trigger() function.

However, two arguments are required by this function for it to work:

  • the name of the animation
  • and an array of metadata

The name can be whatever you want but I recommend keeping it short yet descriptive.

With the naming completed, let's have a look at each of these metadata and how they work.

Step #3. Understanding your animation metadata, and the available options

When you think about it, animations can have a lot of requirements:

  • When should the animation run?
  • Should the animation be applied to child elements?
  • Does every child element get a different animation?
  • Should we add a delay between each animation?

Answering these questions will affect the outcome of the animation, and so for this reason, Angular exports dozens of functions for handling various scenarios.

These functions will create the metadata necessary for helping Angular understand the animations. The most common functions are transition(), style(), and animate(), so let's break them down.

Transition metadata

The first type of metadata is transition metadata, which can be created with the transition() function.

trigger('fade', [
  transition(':enter', [])
])

The transition() function describes when an animation should begin playing.

An expression can be passed as the first argument, and this particular expression will act as the condition for when an animation can start.

In this example, Angular provides an alias for elements entering the document (via the ngIf or ngFor directives) called :enter.

For a complete list of possible expressions, check out this documentation link.

The second argument is an array of metadata that can be applied to the element if the animation should begin playing.

Before getting into animations, let's add another transition() for when an element is leaving the document like so:

trigger('fade', [
  transition(':enter', []),
  transition(':leave', [])
])

Note:

It's completely acceptable to add multiple animations for different conditions. In this example, we will add a fade animation for when an element is entering or leaving the document.

Style metadata

Style metadata will describe the CSS styles that should be applied to the element. Like any animation, you will want to apply styles to an element before, during, and after the animation. In all these events, you will be able to do so by using the style() function.

transition(':enter', [
  style({ opacity: 0 })
]),
transition(':leave', [
  style({ opacity: 1 }),
])

We can set the initial styles of an element by passing this function into the array. This function accepts an object of CSS properties to apply to an element.

Animate metadata

An animation step for an element can also be added by using the animate() function.

This function has two arguments: the duration (milliseconds) and styles to transition to.

transition(':enter', [
  style({ opacity: 0 }),
  animate(1000, style({ opacity: 1 }))
]),
transition(':leave', [
  style({ opacity: 1 }),
  animate(1000, style({ opacity: 0 }))
])

In the above example, we're setting the initial opacity of an element to 0 and animating the opacity to 1, with a duration of 1000ms.

Then, we've reversed the effect for elements leaving the document.

Overall, our animation definition looks like the following:

@Component({
  animations: [
    trigger('fade', [
      transition(':enter', [
        style({ opacity: 0 }),
        animate(1000, style({ opacity: 1 }))
      ]),
      transition(':leave', [
        style({ opacity: 1 }),
        animate(1000, style({ opacity: 0 }))
      ])
    ])
  ]
})

#4. Applying animations to elements

After defining an animation, we can begin applying it to our elements by using the following syntax @<AnimationName>.

For example, let's imagine we had an array of events in our component:

events = [
  {
    time: '08:00 - 09:00 AM',
    action: 'Eat Breakfast',
    bgColor: 'bg-amber-100'
  },
  {
    time: '10:00 - 11:00 AM',
    action: 'Buy Groceries',
    bgColor: 'bg-teal-100'
  },
  {
    time: '01:00 - 02:00 PM',
    action: 'Workout',
    bgColor: 'bg-indigo-100'
  },
  {
    time: '03:00 - 04:00 PM',
    action: 'Release Event',
    bgColor: 'bg-lime-100'
  }
]

Next, let's create a property for toggling elements from within the component.

showEvents = true

Lastly, we can create the UI for adding the animation:

<div class="flex justify-between border-b border-b-slate-200 min-h-[400px]">
  <div class="bg-violet-50 flex justify-center flex-col p-8">
    <h1 class="relative mb-4 text-4xl h-12">
      <span class="absolute top-0.5 left-0.5 text-rose-400 z-10">Fade</span>
      <span class="absolute z-20">Fade</span>
    </h1>
    <button type="button" class="py-2 px-8 first-letter:mb-2 text-white bg-lime-500"
      (click)="showEvents = !showEvents">Toggle</button>
  </div>
  <div class="p-8 w-full flex justify-center flex-col">
    <ng-container *ngIf="showEvents">
      <div 
        *ngFor="let e of events" 
        [ngClass]="e.bgColor" 
        class="item p-4 rounded-xl w-full mb-4" 
        @fade
      >
        <div class="text-sm">{{ e.time }}</div>
        <div class="font-bold">{{ e.action }}</div>
      </div>
    </ng-container>
  </div>
</div>

The above example is using Tailwind for styling the elements, and inside the component's template, we are looping through the events property.

Most importantly, on the same element that's being looped through, we've applied the fade animation.

You can try testing the animation by clicking on the button that will toggle the element's appearance.

GIF video

Animation Events

There's one problem with this animation, and it's that you can rapidly click on the button for toggling the animation, which causes the elements to restart and appear multiple times.

Not great so how do we solve this?

Well, we can wait for the animation to be complete before triggering the animation again by using events.

All animations have two events called start and end, which will be called when an animation starts and ends, respectively.

First, let's update our component's class to store a property for storing the current status of the animation, along with a method for checking this property before toggling the elements.

toggleFlag = false

toggleEvents() {
  if(!this.toggleFlag) {
    this.showEvents = !this.showEvents
  }
}

Next, we can update the click event on the button to use the toggleEvents() method.

<button type="button" class="py-2 px-8 first-letter:mb-2 text-white bg-lime-500" (click)="showEvents = !showEvents">Toggle</button>

Lastly, we can update the element with the animation by adding the start and end events to toggle the toggleFlag property.

<div 
  *ngFor="let e of events" 
  [ngClass]="e.bgColor" 
  class="item p-4 rounded-xl w-full mb-4" 
  @fade
  (@fade.start)="toggleFlag = true"
  (@fade.done)="toggleFlag = false"
>
  ...
</div>

After making these changes, the animations should not overlap one another. Instead, they will wait for an animation to finish before starting another.

State metadata

State metadata describes the final state of an element after an animation is finished playing. While not required, we can describe the element's appearance for different states, and this feature can make our code more readable.

State metadata can be created with the state() function.

The state() method has two arguments, which are the name of the state and the styles to apply to the element.

Here's an example.

trigger('fade', [
  state('void', style({ opacity: 0 })),
  state('*', style({ opacity: 1 })),

  transition(':enter, :leave', [
    animate(1000)
  ])
])

We've completely changed the fade animation's metadata, but the result will be the same.

Let's break down the changes:

  • We are defining two states called void and *. The void state describes the element after it's been removed from the document. This is the official name of the state given by Angular to elements that are not in the document. The * state describes an element after it's been added to the document. Also known as the default state.
  • The void state will have an opacity of 0, whereas the * state will have an opacity of 1.
  • The transition() function has been updated to include both the :enter and :leave transitions. Inside this function, we are adding the animate() function without adding the styles. Angular will be intelligent enough to refer to our states for the styles that should be applied to the element based on the action.

Using the state() function is optional, but it can be a great way to reduce the amount of code you have to write for an animation.

#5. Setting up a fadeSlide animation

Animations can also be tailored to different elements, so let's look at a more complex example called fadeSlide.

trigger('fadeSlide', [
  transition(':enter', [
    query('.item:nth-child(odd)', [
      style({ opacity: 0, transform: 'translateX(-250px)' }),
      animate(
        1000,
        style({ opacity: 1, transform: 'translateX(0)' })
      )
    ]),
    query('.item:nth-child(even)', [
      style({ opacity: 0, transform: 'translateX(250px)' }),
      animate(
        1000,
        style({ opacity: 1, transform: 'translateX(0)' })
      )
    ])
  ]),
  transition(':leave', [
    query('.item:nth-child(odd)', [
      animate(
        1000,
        style({ opacity: 0, transform: 'translateX(-250px)' })
      )
    ]),
    query('.item:nth-child(even)', [
      animate(
        1000,
        style({ opacity: 0, transform: 'translateX(250px)' })
      ),
    ])
  ])
])

This animation will slide an element to the left or right based on a query, and Query metadata will allow us to select specific elements from within the current animation.

Note:

We don't have to always have apply an animation directly to the element, as we can perform a query with the query() function instead.

This function has two arguments:

  • a valid CSS selector
  • and an array of metadata

In this example, we're applying animations to odd and even elements. Odd elements will slide left, while even elements will slide right.

Group Metadata

Group metadata allows us to group animations so that they're played simultaneously, instead of the default which is to be played one after another.

Right now this current animation will run in a sequence where the first element will slide left before the next element starts and slides right.

We want them to run all at the same time, and so to avoid this issue, we can play both animations simultaneously by grouping them with the group() function like so:

trigger('fadeSlide', [
  transition(':enter', [
    group([
      query('.item:nth-child(odd)', [
        style({ opacity: 0, transform: 'translateX(-250px)' }),
        animate(
          1000,
          style({ opacity: 1, transform: 'translateX(0)' })
        )
      ]),
      query('.item:nth-child(even)', [
        style({ opacity: 0, transform: 'translateX(250px)' }),
        animate(
          1000,
          style({ opacity: 1, transform: 'translateX(0)' })
        )
      ])
    ])
  ]),
  transition(':leave', [
    group([
      query('.item:nth-child(odd)', [
        animate(
          1000,
          style({ opacity: 0, transform: 'translateX(-250px)' })
        )
      ]),
      query('.item:nth-child(even)', [
        animate(
          1000,
          style({ opacity: 0, transform: 'translateX(250px)' })
        ),
      ])
    ])
  ])
])

An array of metadata can be passed into this function, and the query() functions are wrapped with the group() function.

After making those changes, we can apply the new grouped version by moving the animation to the parent element:

<div *ngIf="showEvents" 
  @fadeSlide
  (@fadeSlide.start)="toggleFlag = true"
  (@fadeSlide.done)="toggleFlag = false"
>
  <div 
    *ngFor="let e of events" 
    [ngClass]="e.bgColor" 
    class="item p-4 rounded-xl w-full mb-4" 
  >
    <div class="text-sm">{{ e.time }}</div>
    <div class="font-bold">{{ e.action }}</div>
  </div>
</div>

In this example, we're applying the fadeSlide animation on the <div> element with the ngIf element.

But why?

Good question! In order to use our query, we must move the animation to the parent element wrapped around the loop. Otherwise, we wouldn't be able to select even and odd elements.

GIF video

So now it's your turn. Start adding animations to your own Angular apps today!

Pretty simple right?

The beauty of this package is it allows Angular to provide even more functions for fine tuning animations, and adding interactivity to an application is never a bad idea. As we shared in the introduction, even simple animations can help boost user experience!

Hopefully, this post gave you insight into how you can get started with your own Animations in Angular.

If you want to learn even more animations then read this guide from Angular to learn more, or you can check out my Complete Angular Developer course for 2022 for a guided walkthrough with more examples, and in-depth training on other some of the other important elements of Angular.

More from Zero To Mastery

Top 10 Angular Projects For Beginners And Beyond preview
Popular
Top 10 Angular Projects For Beginners And Beyond

Are you looking to skill up in Angular? We give you 10 Angular practice projects to make that happen plus our Top 3 you can't miss for you to up your game!

Top 5 Reasons Why You Should Learn Angular preview
Top 5 Reasons Why You Should Learn Angular

Lightning-fast performance, helpful tooling, and a vibrant community are the pillars of any framework worth it's salt. Angular offers these and so much more.

Angular vs React vs Vue: The Best Framework for 2024 is… preview
Angular vs React vs Vue: The Best Framework for 2024 is…

We are going to look at job postings, developer statistics, download numbers, and other factors, to actually burst through the fuzzy cloud that is React, Angular and Vue to help you decide what you should learn for your career.