Angular Animations: Router 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:
- Learn the Basics
- Enter and Leave Animations
- The Keyframes Function
- Query and Stagger Function
- Start and Done Events
- Parallel Animations
- Animating to an unknown height
- Adding Flexibility with Params
- Creating Reusable Animations
- Disable and Enable Animations
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.
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:
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 {
}
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',
}
Or if we navigate to the “contact” path, the ContactComponent will be displayed.
{
path: 'contact',
component: ContactComponent,
title: 'Contact Us',
}
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', [
]);
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('* => *', [
])
]);
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 })
])
]);
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 })
])
]);
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 })
])
]);
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 {
}
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.
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>
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) {
}
}
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>
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()
]
});
Ok, so that should be everything we need to properly transition when navigating between routes. So, let’s save and try it out.
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', ...)
], ...)
])
]);
Now let’s save and try it again.
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.