Angular4 Router Fade Transitions

Implementing a sequence-fade animation for a router outlet (Ng4.4.4)

This live code preview can be edited on StackBlitz

This implementation has three parts: an animation, a binding, and a little CSS. These three parts work together to add fade-in and fade-out animations to all components managed by a given router outlet.

The fade-in of the new component happens after the fade out of the previous component. That is, this is not a cross-fade solution, it is a sequence-fade solution.

The transition functionality is managed entirely by the component which hosts the router-outlet. The child components do not need to be modified.

Just what you were looking for? Please clap for this article!


1. Add this animation file to your project

2. Reference it in your host component’s animations metadata:

animations: [fadeAnimation]

3. Add an ID to your router outlet, and add an animation trigger to its parent.

<main [@fadeAnimation]="getRouterOutletState(o)">
<router-outlet #o="outlet"></router-outlet>

4. Add a function to return the outlet’s current state:

public getRouterOutletState(outlet) {
return outlet.isActivated ? outlet.activatedRoute : '';

5. Use absolute positioning for all router-outlet children:

:host {
  display: flex;
overflow: hidden;
height: 100vh;
  main {
position: relative;
  /deep/ router-outlet ~ * {
position: absolute;
width: 100%;
height: 100%;

Full walkthrough below…

The Animation

This file defines the animation to be used for transitioning all router-outlet children. It handles both the fade-in and fade-out parts of the animation.

A breakdown of the animation is included below.

The trigger

This is the animation name we will use when binding the animation to an expression which will trigger it.

trigger('fadeAnimation', [

Multiple states and transitions can be defined within a single trigger, but we will have only one transition. The trigger runs its transitions when its binding updates. The binding part is done in a host component, which we’ll look at shortly.

The transition

This bit identifies which state changes to transition. We use wildcard states to capture all state changes.

transition('* => *', [

You could use transition(':enter', []) or transition(':leave', []) to target the “component added” or “component removed” state changes specifically, but that would not allow you to run the fade-out and fade-in animations in series. That is, you would get a cross-fade not a sequence-fade.

A single transition expects to receive an array, which is a series of animation steps to execute as a sequence. The array can contain a mixture of style, animate or query definitions. The one we will use is query.

See the Angular Guide or the API Documentation for more information on the transition state selectors.

The queries

A query allows us to find and animate elements within the current host component, rather than animating the component itself. This is the trick which allows us to define the transition animation once on the parent, rather than once for every child we want to transition.

query(':enter', [
query(':leave', [
query(':enter', [

These queries will execute in order. Each query’s animation steps must complete before the next query is run. The formatting of these little things is pretty gross, bear with me…

The first query says find an element with the state :enter and set its opacity to zero. This is because the component for the new state is added immediately, but we don’t want to see it until the previous one has disappeared.

style({ opacity: 0 })
{ optional: true }

The second query says find an element with the state :leave. Set its opacity to 1, then animate the opacity to 0. This is the fade-out animation.

style({ opacity: 1 }),
animate('0.2s', style({ opacity: 0 }))
{ optional: true }

The third query says find the :enter element again but this time animate its opacity from 0 to 1. This is the fade-in animation.

style({ opacity: 0 }),
animate('0.2s', style({ opacity: 1 }))
{ optional: true }

The Binding

In the component which has a child router-outlet you need to import and configure the animation. For context, here’s a sample component. Explanation below the code.

Import the animation
Import the animation we defined earlier, from wherever you saved it. Mine is in an animations folder as demonstrated on line 5.

The animation metadata
You need to add the animation to your component definition, demonstrated on line 10 above. This makes the animation available to your component.

The router state getter function
Add a function (line 16) which takes the router outlet as a parameter, and returns the router outlet’s activatedRoute, which is its current state. The router outlet’s activatedRoute will be updated when navigation occurs for the given router outlet, and will therefore trigger the animation. To protect against errors, we must check the router outlet is activated before accessing its activatedRoute.

The Styles

Because of our animations, during a transition both the previous and the next component will be present in the DOM at the same time. To prevent them from stacking up and ruining our layout, we need to overlay them.

The easiest way to do this is to give your router-outlet’s parent element the style position: relative; which means other elements can then align themselves to it.

The child components of the router can be accessed using the selector /deep/ router-outlet ~ *. These elements should use position: absolute; to ensure they overlay one another.

The rest of the CSS is optional and can be modified. Here I am using display: flex; to make the main element always fill the available space.

All done! Enjoy your route animations :)

12 Oct 2017 — Updated for Angular 4.4.4 and added live demo

Like what you read? Give Tanya Gray a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.