Angular Animations Tutorial: Route Transitions

brianmtreese

Brian Treese

Posted on June 14, 2024

Angular Animations Tutorial: Route Transitions

If you’ve ever worked with an Angular application that has routing, you may have wanted to add transitions as you navigate between routes. It just makes the app feel more elegant overall. Well, if you didn’t know, this is totally doable with the Animation module and in this example, I’ll show you just how easy it is. Alright, let’s get to it.

Before We Get Started

Now, before we get too far along, it’s important to note that I’ve already created several posts focused on the animation framework in Angular.

Angular Animation Tutorials:

These posts cover many different animation topics so if any of these concepts look unfamiliar to you, you’ll probably want to check these posts out first, so that you’re not lost in this example.

And, to make them easier to find, I’ve created an Angular Animations playlist on my YouTube channel to help, so check it out!

Ok, enough of that, onto the example for this post.

The Demo Application

For this example we’ll be using this simple demo application. We have a few different pages that we can navigate to. This app has already been set up with routing so when we click the links in the main nav we will navigate to the appropriate page.

Example of a simple application built with Angular and the Angular Routing Module and no route transitions

But when we navigate to the different pages, it would be better if we had some sort of transition. Maybe some sort of crossfade as we’re seeing here:

Example of a simple application built with Angular and the Angular Routing Module and and route transitions

Well, this is exactly what we’re going to do in this example. But first, let’s look at the existing code to better understand what we need.

The Existing Code

Ok, so like mentioned, this app has already been set up with routing. So, if we take a look at the app component, in the template we have a router-outlet.

main.ts

@Component({
    selector: 'app-root',
    template: `
        <app-nav></app-nav>
        <router-outlet></router-outlet>
    `,
    ...
})
export class App {
}
Enter fullscreen mode Exit fullscreen mode

When we click any of the links in the navigation component, the routed component will be inserted as a sibling of the router-outlet element. If we look at the route config, we can see that this is where we’ve provided both the path that we want to see in the address bar as well as the component that we want to display when navigating to that path.

So, when we navigate to the “blog” path for example, the BlogComponent will be displayed.

{
    path: 'blog',
    component: BlogComponent,
    title: 'Our Blog',
}
Enter fullscreen mode Exit fullscreen mode

Or if we navigate to the “contact” path, the ContactComponent will be displayed.

{
    path: 'contact',
    component: ContactComponent,
    title: 'Contact Us',
}
Enter fullscreen mode Exit fullscreen mode

You get the idea. So, what this means as that the active component for the new route will be considered an “entering” item as far as Angular animations are concerned. And the component from the previous path will be considered a “leaving” item. This means, we’ll have a way to animate them both.

Now, if you’re unfamiliar with the concept of “:enter” and “:leave” animations, I’ve got a video here for you, so be sure to check it out to better understand the concept.

Creating the Route Transition Animation

Ok, now that we have an understanding of how this all works currently, let’s start by creating our animation. To do this, we’ll start by adding a new file for the animation code, let’s name it “route-transition.ts”.

Now, let’s add an exportable const so that we’ll be able to import this animation into our app component. Let’s call it "routeTransition". We will set it using the trigger() function from the Angular animations module. For the name, we can call it routeTransition as well.

route-transition.ts

import { trigger } from '@angular/animations';

export const routeTransition = trigger('routeTransition', [
]);
Enter fullscreen mode Exit fullscreen mode

Ok, next we need a transition() function. For this route transition, we will want it to run whenever the route data changes. So, we’ll animate from any state with the asterisk to any other state.

import { ..., transition } from '@angular/animations';

export const routeTransition = trigger('routeTransition', [
    transition('* => *', [
    ])
]);
Enter fullscreen mode Exit fullscreen mode

Now, the first thing that we’ll want to do in this animation is set the item entering to start from a “hidden” state. So, let’s add the query() function to query for the entering component. Then we’ll add the style() function so that we can provide the starting styles. We’ll start with an opacity of zero, and a scale of point nine. The last thing we need to do is add the optional flag for when no entering items are found.

import { ..., query, style } from '@angular/animations';

export const routeTransition = trigger('routeTransition', [
    transition('* => *', [
        query(':enter', [
            style({ opacity: 0, scale: 0.9 }),
        ], { optional: true })
    ])
]);
Enter fullscreen mode Exit fullscreen mode

Ok, next we’ll transition the leaving component. So, let’s add another query() function and query for leaving items this time.

For this item, we don’t need any starting styles since it will automatically start from a fully opaque, full scaled size. All we need to do is add the animation so we can add the animate() function. To make sure we can really see this animation, let’s start out by animating over one second. Then let’s add the style we’re animating to with another style() function.

We’ll want to animate to an opacity of zero, and a scale of point nine. And then this needs to be optional as well.

import { ..., animate } from '@angular/animations';

export const routeTransition = trigger('routeTransition', [
    transition('* => *', [
        ...,
        query(':leave', [
            animate('1s', style({ opacity: 0, scale: 0.9 }))
        ], { optional: true })
    ])
]);
Enter fullscreen mode Exit fullscreen mode

Ok, the last thing we need to do is animate the entering item to its final visible state. So, let’s add another query() and query for entering items.

Since we already set its starting style, we can just add the animate() function to animate to the final state. We’ll animate over one second again. Then let’s add another style function and we’ll animate to an opacity of one, and a scale of one too. Then we just need to make it optional.

export const routeTransition = trigger('routeTransition', [
    transition('* => *', [
        ...,
        query(':enter', [
            animate('1s', style({ opacity: 1, scale: 1 }))
        ], { optional: true })
    ])
]);
Enter fullscreen mode Exit fullscreen mode

Ok, that should be everything we need for the animation. Now we can switch over and add it to the app component.

Adding the Route Transition Animation to the Parent Component

To use the animation, let’s first add the animations array in our component metadata. Within this array, let’s add our new "routeTransition" animation.

main.ts

import { routeTransition } from './route-transition';

@Component({
    selector: 'app-root',
    ...,
    animations: [
        routeTransition
    ]
})
export class App {
}
Enter fullscreen mode Exit fullscreen mode

Ok, so now we can wire this up But before we do, it’s important to understand how this layout works. It uses a grid. The first column is for the navigation and the second column is for the routed components. Anything that is a sibling of the router-outlet will be placed into the second grid column, meaning both the entering and leaving items will exist within this column on top of one another.

Diagram explaining the columns of the overall grid layout

The bummer here is that we need to add a container around the router-outlet in order to properly bind our animation since it needs to be able to query for entering and leaving items.

But, that’s ok, we can set it to display: contents so it will essentially be invisible. So, let’s add a div and on this div let’s add a style with display, contents.

<div style="display: contents">
    <router-outlet></router-outlet>
</div>
Enter fullscreen mode Exit fullscreen mode

Ok so this is where we’ll bind our animation trigger, but what will we bind it to in order to trigger it when changing routes?

Triggering the Route Transition when Changing Routes

Well, for this we can use the ActivatedRoute snapshot data object.

To do this, we need to add a constructor. Then we need to inject in the ActivatedRoute. Let’s create a protected field named “route”, and then we need to inject in the ActivatedRoute class.

import { ..., ActivatedRoute } from '@angular/router';

@Component({
    selector: 'app-root',
    ...
})
export class App {
    constructor(protected route: ActivatedRoute) {
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, let’s bind our animation trigger on the div. We’ll bind to the route, snapshot, data object. This object is updated every time the route changes so it should properly trigger our animation.

<div [@routeTransition]="route.snapshot.data" style="display: contents">
    <router-outlet></router-outlet>
</div>
Enter fullscreen mode Exit fullscreen mode

Ok, we’re almost there, but before this animation will run, we need to enable animations by adding the provideAnimations() function to our providers array.

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

bootstrapApplication(App, {
    providers: [ 
        ...,
        provideAnimations()
    ]
});
Enter fullscreen mode Exit fullscreen mode

Ok, so that should be everything we need to properly transition when navigating between routes. So, let’s save and try it out.

Example of Angular Route Transitions working correctly but with a very slow duration

Nice, looks like it’s properly animating both the component that is leaving and then animating the component that is entering. Now, it looks a little odd, primarily because of how slow it animates but, if you remember, we are animating over one second for the leaving item and then one more second for the entering item. This is pretty slow for these types of transitions, but I just really wanted to illustrate how this animation works.

Now that we can see it working, and understand it, let’s switch to a duration like point two seconds instead.

route-transition.ts

export const routeTransition = trigger('routeTransition', [
    transition('* => *', [
        ...,
        query(':leave', [
            animate('0.2s', ...)
        ], ...),
        query(':enter', [
            animate('0.2s', ...)
        ], ...)
    ])
]);
Enter fullscreen mode Exit fullscreen mode

Now let’s save and try it again.

Example of Angular Route Transitions working correctly but with a better, quicker duration

There, much better.

Conclusion

Of course, there’s many different ways to animate this type of thing, your imagination is really all that’s holding you back because now you know everything else you need to add route transitions in your Angular applications.

Now, there’s still plenty more to cover on angular animations so I’ll go ahead and stop here for now, but keep an eye out for more posts in the future.

Want to See It in Action?

Check out the demo code and examples of these techniques in the in the Stackblitz example below. If you have any questions or thoughts, don’t hesitate to leave a comment.

💖 💪 🙅 🚩
brianmtreese
Brian Treese

Posted on June 14, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related