In this Article

Angular and its router are powerful tools to create single-page applications (SPAs). However, there are some critical differences in the context of an Ionic Framework application. Ionic Framework (short Ionic) comes with many new components and features, such as its router and animation system. Ionic's router enables us to write routing animations and page transitions in a simple manner. It even applies them to lazy loaded modules and pages – especially the last part is a more complex task in a standard Angular application.

The included Ionic animations system is framework-agnostic, and it leverages the Web Animations API to create performant and resource-saving animations. It is available with the Ionic Framework version 5.0 and upward.

Version Information:

  • Angular: 9+
  • Ionic Framework 5.0

Let's start by taking a look at the Ionic router and explaining its introduced feature set. Afterward, we add navigation transitions using the Ionic animation system.

Differences Between Angular's and Ionic's Router

The Ionic Framework router extends the Angular router. You can use them next to each other, for example, in nested routes. However, some features are only available to the Ionic router, such as its page transition system and an internal history stack. The most significant difference between these two routers is that Ionic's router does not remove loaded "pages" from the DOM, but it tracks them in an internal state and hidden in the DOM. That enables us to reuse and animate once-loaded pages because components will not be reinitialized; instead, they become just visible again. It can also speed up the load time and responsiveness on specific mobile scenarios.

How does the Ionic router keep track of opened and closed pages?

Ionic's Navigation State

Every time a page is visited, it is also added to the stack and later hidden in the DOM; Angular, in contrast, removes it completely. But it keeps them available and hidden until they are popped off their internal state. The stack and hidden DOM elements are critical for Ionics page transitions. It enables Ionic to pre-load pages offscreen and then start the desired animation. The pre-loading also makes it easy to manage the animation because all elements are available and freely manipulated.

You can observe the behavior by opening the dev tools and inspecting the DOM nodes containing the router. You should see something similar to this:

Ionics router stack, showing hidden DOM elements

An element is only removed from the stack if it is actively cleared or popped; this can be done either by navigating back using methods provided by Ionic's framework or navigating back with the browser. However, navigating "forward" with the browser does not add the page back to the stack or plays its transition.

One method provided by Ionic is one of its UI elements, the back-button component or the routerDirection directive to remove the last element from the stack.

<ion-back-button defaultHref="/"></ion-back-button>
<a routerLink="/path/to/page" routerDirection="back">Back to…</a>

Alternatively, you can invoke the pop or navigateBack method on the NavController class.

import { NavController, Platform } from '@ionic/angular';

private navController: NavController,

this.navController.pop();
this.navController.navigateBack('path/to/page');

The pop is the recommended way to go back one step in history - in terms of the Ionic navigation stack and using the NavControler. The navigateBack method will go back in history and pop every state until the given path is found. Popping or going back in history will also display the back page transition to the target page and remove the page from the stack and the DOM.

Since pages are not directly added or removed from the DOM anymore and the OnInit and OnDestroy are not called as expected, we need a new way to interact with their lifecycle.

Kostenloses Cheat Sheet 'Ionic für Web-Apps'

Max Schulte zeigt Ihnen auf wenigen Seiten übersichtlich zusammengefasst, was Sie bei der Arbeit mit Ionic wissen sollten. Melden Sie sich kostenlos zu unserem Newsletter an, um das Cheat Sheet per E-Mail zu erhalten.

New Lifecycle Callbacks

Ionic Framework also introduces new component lifecycle steps, and this is necessary due to the loading/offloading behavior of the router.

Removing a page from the view but not from the DOM renders it mandatory to provide interfaces for this new behavior. Angular does not call the OnInit or OnDestroy and other lifecycle methods as long as the component is not removed from or added to the DOM. This chart shows the new lifecycle methods by Ionic and on which events and in which order they are called:

Showing Ionic's lifecycle methods compared to Angular's OnInit and OnDestroy

Adding Navigation Animations with Ionic

Let's define and handle the page transition animation on a global scope using Ionic's animation system. We plan to create a page transition like shown below:

Simple Ionic ionic router transition / animation

Let's start with the function definition: We are receiving two inputs. The first one is the affected router outlet and can be ignored for this use case. The second one contains all the required information.

export const customAnimation = (_: HTMLElement, opts: any): Animation {
  ...
}

Ionic does not export the type definition for the second import, but we can grab it from the sources (Source 1, Source 2, Source 3) our self. For the animation, we are interested in the entering and leaving DOM element and the animation configuration direction and duration.

This is how a typical options object looks:

export interface AnimationOptions {
  mode: 'md' | 'ios';
  animated: boolean;
  direction: 'root' | 'forward' | 'back';
  enteringEl: HTMLElement;
  leavingEl: HTMLElement;
  baseEl: HTMLElement;
  progressAnimation: boolean;
  showGoBack: boolean;
  animationBuilder: (_, opts) => {}
  progressionCallback?: () => {};
  duration?: number;
}

Let's use Ionic's animation system to create the desired page transition effect. First, we create an animation controller with some default configurations for all transitions.

export const customAnimation = (_: HTMLElement, opts: any): Animation {
  // create root transition
  const rootTransition = animationCtrl
    .create()
    .duration(opts.duration || 333)
    .easing('cubic-bezier(0.7,0,0.3,1)');
}

Afterward, we start defining the transitions for the elements we want to animate.

const enterTransition = animationCtrl.create().addElement(opts.enteringEl);
const exitTransition = animationCtrl.create().addElement(opts.leavingEl);
}

We can define different animations, depending on the navigation direction. In this case, going forward shifts a page in from the left, and going back reverses the animation. We are doing so by defining key points for a property - where to start the animation and with what value it should end. The framework then tweens between these two points using the Web Animations API wherever possible.

enterTransition.fromTo('opacity', '0', '1');
exitTransition.fromTo('opacity', '1', '0');

// handle animation depending on direction
if (opts.direction === 'forward') {
  enterTransition.fromTo('transform', 'translateX(-1.5%)', 'translateX(0%)');
  exitTransition.fromTo('transform', 'translateX(0%)', 'translateX(1.5%)');
} else {
  enterTransition.fromTo('transform', 'translateX(1.5%)', 'translateX(0%)');
  exitTransition.fromTo('transform', 'translateX(0%)', 'translateX(-1.5%)');
}

Last but not least, we add our transitions to the preconfigured root transition and return it.

// add transition to root and return root
...
rootTransition.addAnimation([enterTransition, exitTransition]);
return rootTransition;

The complete function should look like the following code:

export const customAnimation = (_: HTMLElement, opts: any): Animation {
  // create root transition
  const rootTransition = animationCtrl
    .create()
    .duration(opts.duration || 333)
    .easing('cubic-bezier(0.7,0,0.3,1)');

  const enterTransition = animationCtrl.create().addElement(opts.enteringEl);
  const exitTransition = animationCtrl.create().addElement(opts.leavingEl);
    
  enterTransition.fromTo('opacity', '0', '1');
  exitTransition.fromTo('opacity', '1', '0');
    
  if (opts.direction === 'forward') {
    enterTransition.fromTo('transform', 'translateX(-1.5%)', 'translateX(0%)');
    exitTransition.fromTo('transform', 'translateX(0%)', 'translateX(1.5%)');
  } else {
    enterTransition.fromTo('transform', 'translateX(1.5%)', 'translateX(0%)');
    exitTransition.fromTo('transform', 'translateX(0%)', 'translateX(-1.5%)');
  }
  
  rootTransition.addAnimation([enterTransition, exitTransition]);
  return rootTransition;
}

Finally, we can tell Ionic to use our custom animation for navigation. We are doing so by providing the method to the IonicModule configuration in our app module.

@NgModule({
  ...
  imports: [
    ...    
    IonicModule.forRoot({navAnimation: customAnimation})
  ]
})

Let's take a look at the animation just created. It is the same as displayed above.

Simple Ionic ionic router transition / animation

The leaving page fades away, and the incoming page fades in. We also see the movement matching the navigation direction, creating a smooth user experience. That way, the user can quickly grasp what happens and won't lose focus on his task.

Source Code and Live Demo:

Stackblitz: https://stackblitz.com/edit/github-sxhycm-52soeu?

Conclusion

Using animations helps the user to understand changes. It eases cognitive load and can make complex changes comprehendible. Page changes can be hard to process, multiple parts can change, or an entirely new context is created. Ionic's animation system and router make it easy to animate those changes and guide the user through the process without losing its focus. We can even write custom animations and pinpoint the user experience. Those animations are applied to lazy-loaded modules and pages, too. The produced custom animation code is easy to read and extendable, thanks to the provided animation API.

Ionic's Router and animations API must not be feared. They provide a straightforward API and integrate well with Angular. Some challenging tasks even become easier using Ionic's approach.

If you don't want to miss out on more articles, webinars, and screencasts, sign up for our newsletter.

Related Articles

 | Max Schulte

Today not every platform-native API is available to the browser and therefore not available for a web application. That is where additional frameworks like Capacitor and the Ionic Framework take over and close the gap between native applications and web applications. In this…

Read article
 | Max Schulte

Through web APIs, the browser gives you access to those features. Some API parts are easy to use; others require user permission and a secure context. This is where Ionic's Capacitor comes into play. It provides you with unified access to those APIs. But how does Capacitor help…

Read article
 | Christian Liebel

In the previous parts of this article series, we learned that Apple does not support the standardized web-based push mechanisms, and there is no sign of a possible timeline for implementation. Therefore we have to look at additional ways to bring the users' attention back to our…

Read article