Angular4 Router Fade Transitions

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

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.


TL:DR;

1. Add this animation file to your project

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

animations: [fadeAnimation]

3. Inject ActivatedRoute as a dependency for your host component:

constructor( public activatedRoute: ActivatedRoute ) { }

4. Add a HostBinding linking up the animation to the router:

@HostBinding('@fadeAnimation`) 
public get childRouteTransition() {
return this.activatedRoute.snapshot;
}

5. Ensure your host absolutely positions its route components.

:host {
  position: relative;
display: block;
width: 100vw;
height: 100vh;
  /deep/ router-outlet + * {
position: absolute;
top: 0;
left: 0;
width: 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.

query(':enter', 
[
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.

query(':leave',
[
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.

query(':enter', 
[
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 11 above. This makes the animation available to your component.

Add ActivatedRoute dependency
This gives us access to the current state of the router, so we can easily check for changes. Add the dependency in your constructor (line 18).

The Host Binding
Add a HostBinding (line 15) which will apply the animation to your component’s DOM element. Immediately after it, write a function which will determine when the animation should be run. We want ours to run when this.activatedRoute.snapshot changes, which represents a change of route.


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 host 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; and should be aligned to the top and left of their parent container.

The rest of the CSS is optional and can be modified.


All done! Enjoy your route animations :)

Show your support

Clapping shows how much you appreciated Tanya Gray’s story.