How to animate your App Router transitions using the new animation features in Angular

Few months ago I shared with you an experimental technique to animate Router transitions. Since then there have been few releases introducing new features. Today we can achieve the same results more elegantly and using less code. Let’s see how we can use them to create transitions never seen before!

Use the links below to run the demos:

Basic | Variation | Stagger | Final

Find the latest Angular content following my feed at @gerardsans.

Demo Application

To explain the new approach, we will use a simple Application with only two sections: Home and About. We will implement a cool Router transition that slides the content to the left and uses staggering as seen in the image below.

Awesome Router transitions

Animations Setup

Starting with Angular v4 animations live in a separate bundle, so you can make your Apps smaller if you don’t need them.

First of all, you need to add the following dependencies to your project: @angular/animations and @angular/platform-browser/animations . Next, include BrowserAnimationsModule to your root module (lines 3 and 6).

if you need to use browsers that don’t support Web Animations API natively: IE/Edge, Safari, etc; you must include the web-animations polyfill.

As we have seen, our demo Application will be composed by a top navigation and the main content. The top navigation will be shared between sections (lines 4–7). We use the router-outlet element to tell the Router where we want to render our components when matched with a route (line 9).

We used static routes to create the different navigation links (lines 5–6). To style the current section, we used the routerLinkActive directive. So for example, when we navigate to Home, it will add the active class and change rendering accordingly.

<a href="#/home" class="active">Home</a>

Adding Router transitions

Let’s change the default setup to introduce Router transitions. First, we need to add routerTransition , our animation trigger, to the component metadata (line 3). Then, we can bind @routerTransition in the main element, so later on, we can style the inner Route Components instantiated by the Router (line 5).

You can use a div or any other element instead of main as long as is a parent of router-outlet.

We use getState passing the outlet reference (line 6) to set the right state. This function will return the state property as set on the route definition. We will see that in the next section. This configuration will allow us to control which transition is executed for each route.

Note: we can get hold of the outlet reference by using #o=”outlet” . This is possible due to the use of exportAs in the router-outlet definition. The combination give us quick access to the underlaying RouterOutlet class.

Setting up routes

In Angular, the Router tries to match a route definition with the current url following the same order used during setup.

The main routes (lines 3–4) tell the Router to instantiate the Home and About components when navigation matches their respective paths. Notice the data property setting each state to the corresponding route. These states have to match the transitions as defined in our routerTransition trigger.

We also used two special routes for common behaviours. The route with empty path (line 2) covers the default route which will be empty unless we are navigating to a specific url. In that case, we indicated to redirect using the home route. The otherwise route (line 5) which will catch any typos or undefined routes displaying a user-friendly 404 page.

For this demo, we are using the hash location strategy (line 9) due to Plunker. If we had access to the backend, we could also use the path location strategy. This would require redirecting undefined routes to index.html to avoid 404 errors from the server.

Angular Animations

Let’s briefly introduce Angular animations. Angular is based on top of the Web Animations API, we use animation triggers to define a series of states and transitions between states. We use styles that help us build the desired effect using CSS properties.

The main principle behind animations is that once we set the initial and final state using styles, the intermediate states are being calculated for us.

Who else to explain animations better than the party parrot!? 😃

We can set the duration of our animations by using a value in seconds or milliseconds. Ex: 1s, 1.2s, 200ms. Sometimes we may also want to control the timing function which sets the pace in which the intermediate steps, tweens, are calculated. The default timing is ease ; other common values are linear or ease-in-out . You can play with some other options using this tool created by Lea Verou.

Since Angular v4.2+ we can also use sequence and group to run animations one after the other or in parallel; and query to access child elements and stagger to create nicely chained choreographies.

These events will trigger an animation:

attaching or detaching an element to/from the view

changing a state bound to a trigger. Eg: [@routerTransition]=”home” .

In the context of Router transitions, note that Components are being removed and added to the view as part of the navigation.

Animation Definition

First let’s see how we can use Angular Animations to slide the content to the left.

Basic Router transition

Initially, we define our trigger as routerTransition (line 3). In this implementation we used a generic transition covering all possible states (lines 4–17). This is a refactor from using two separate transitions: ‘* => home’ and ‘* => about’ .

We can use two special states void and * (asterisk) in our transitions that represent: an element not yet attached to the view and any possible state.

We defined few items for our generic transition that will execute sequentially as defined in the array used as second argument (line 4).

The first one (lines 6–7), uses the new query command to select both the new route component and the leaving one; query expects a CSS selector as the first argument including some special keywords like :enter , :leave and * .

In this first query, :enter and :leave will match Route Components being attached to or removed from the view. Note how we can use multiple selectors separating them with commas (line 6). Once we have these Route Components, we set their styles to achieve the sliding effect (line 6). By using { position: fixed } the components will be placed according in the viewport and slide through the page.

Following, we have a group that will make the execution of the inner animations run in parallel.

Let’s imagine we are navigating from Home to About. The first query will match the component being added ( :enter ), which is the About Component. We start by setting an initial style positioning the component far right (line 10). Then we set the animation to slide it to the final position with an easing function and duration (line 11). The result will be the About Component sliding from the right to the left. For the second query, we used a similar approach, using ( :leave ) that will match the Home Component also sliding far to the left.

CSS tip: we use transform instead of top, bottom, left, right for its better performance.

Note that this implementation for Router transitions is different from the use case in animations where we bind our trigger to the element that we want to animate. This means that we can’t use states to style Route Components as that would apply the styles to the parent (main element in our example) and not to Route Components.

During the Router initialisation some of the queries return empty results, to avoid throwing errors we set the optional parameter in all our queries.

Adding Staggering

Once we have the basic setup working, we can easily add new effects combining query and stagger.

export const routerTransition = trigger('routerTransition', [

transition('* <=> *', [

/* order */

/* 1 */ query(':enter, :leave', ...),

/* 2 */ query('.block', style({ opacity: 0 })),

/* 3 */ group([ // block executes in parallel

query(':enter', [...]),

query(':leave', [...]),

]),

/* 4 */ query(':enter .block', stagger(400, [

style({ transform: 'translateY(100px)' }),

animate('1s ease-in-out',

style({ transform: 'translateY(0px)', opacity: 1 })),

])),

])

])

Finally, we combined transform and opacity to slide some elements up vertically and fade them in. By using stagger we introduced a small delay (in milliseconds) to each animation creating a nice curtain effect. As a final note, only say that we had to introduce a new query to initialise the .block elements with ( opacity: 0 ) so they don’t show while the component slides in.

Final step

As a final twist, I added some more code to reverse the staggering when leaving the Home Component and also added some cubic-bezier functions to get that extra sparkle. See below

Final Solution!

Special Mention

The implementation shown in this article is the same as explained by Matias Niemelä on this blogpost and on these slides from him at ngJapan where you can learn some more cool new animation features.