Create a Sliding Item Animation with a Directive in Ionic 2



·

A common user interface design principle is to only show the user what they need in order to make a decision. Consider your email inbox – you can only see the subject line, usually a little of the body, and some other minor details. This is enough for the user to make a decision as to whether they would like to see more of the email, which they can do by clicking on it.

On the web, we have infinite space to work with, so we could potentially display emails in their entirety right there in the inbox in one giant list (eliminating the need to go to another page to view individual emails). Of course, this would lead to a bad user experience. The screen would appear cluttered with information, and the user would struggle to find what they are looking for among the jungle of stuff they do not care about. This is an obvious and exaggerated example, but the principle can be applied more subtly.

A common user interface element you will likely have seen in many mobile applications, is the ability to swipe a list item to reveal more options or perform some action. Following on with the email example, if you were to view your inbox through the GMail App, there is no visible option to archive emails you no longer need. However, if you swipe any of the emails it reveals the archive option:

Gmail App Swiping

Ionic 2 provides sliding list items by default, but the issue with this particular component is conveying to the user that the item can actually be swiped to reveal more options. Since this is a reasonably common user interaction, users may discover it on their own, but a good user interface should reduce dissonance wherever possible. Essentially, you don’t want your users to have to “figure out” how to use your interface, it should be obvious.

The other day someone on the Ionic Slack brought up this very concern, and I hacked together a quick solution that would allow them to create an animation to hint to the user that this functionality is available, like this:

Ionic 2 Sliding Item Animation

I decided to take it a step further and make it into its own directive that can easily be applied to a sliding item. Not only is this solution a little cleaner, but by having it as a directive we can apply it to the element using an attribute like this:

<ion-item-sliding animateItemSliding>

Again, this is nice and clean, but it also makes it easier to control. Since we can also pass values into directives using data binding:

<ion-item-sliding [animateItemSliding]="shouldAnimate">

we could do things like only display the animation if it is the first time the user is using the app, or maybe we want to keep displaying the animation until the user interacts with the sliding function themselves.

In this tutorial, we are going to walk through how to create a directive to create this animation, and we’re going to cover a bit of theory on both directives and animations along the way.

Before we Get Started

Last updated for Ionic 3.0.1

Before you go through this tutorial, you should have at least a basic understanding of Ionic 2 concepts. You must also already have Ionic 2 set up on your machine.

If you’re not familiar with Ionic 2 already, I’d recommend reading my Ionic 2 Beginners Guide first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic 2, then take a look at Building Mobile Apps with Ionic 2.

1. Generate a New Ionic 2 Application

We are going to create a simple one page application, which you can do by running the following command:

ionic start ionic2-slide-animation blank --v2

once that has finished generating, you should also make it your working directory by running the following command:

cd ionic2-slide-animation

As I mentioned, we are going to create a directive to handle triggering the animation. We are also going to use the Ionic CLI to generate this directive for us by running:

ionic g directive AnimateItemSliding

and then we will need to declare that directive in our applications’ @NgModule.

Modify src/app/app.module.ts to reflect the following:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AnimateItemSliding } from '../components/animate-item-sliding/animate-item-sliding';

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    AnimateItemSliding
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

Now we are all ready to jump into the code.

2. Set up the List

Let’s set up a simple sliding list to start off with so that we have something to work with – I’m not going to explain how the sliding list itself works so if this component is completely new to you I would recommend taking a look at the documentation.

Modify src/pages/home.html to reflect the following:

<ion-header>
  <ion-navbar color="danger">
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>

    <ion-list>

      <ion-item-sliding [animateItemSliding]="shouldAnimate">

        <button ion-item>
          Item
        </button>

        <ion-item-options side="right">
          <button ion-button>Unread</button>
        </ion-item-options>
      </ion-item-sliding>

      <ion-item-sliding>

        <button ion-item>
          Item
        </button>

        <ion-item-options side="right">
          <button ion-button>Unread</button>
        </ion-item-options>
      </ion-item-sliding>

      <ion-item-sliding>

        <button ion-item>
          Item
        </button>

        <ion-item-options side="right">
          <button ion-button>Unread</button>
        </ion-item-options>
      </ion-item-sliding>

      <ion-item-sliding>

        <button ion-item>
          Item
        </button>

        <ion-item-options side="right">
          <button ion-button>Unread</button>
        </ion-item-options>
      </ion-item-sliding>

    </ion-list>

</ion-content>

There is almost nothing special about this code, it’s basically just copied exactly as it is in the documentation. However, there is one very important difference. The first sliding item looks like this:

<ion-item-sliding [animateItemSliding]="shouldAnimate">

We’ve added the animateItemSliding attribute, which will (once we finish implementing it) allow us to apply the functionality of the directive we are creating to this element.

3. Create the Directive

Let’s create that directive now, it’s quite short so I will post the code in its entirety and then walk through it.

Modify src/components/animate-item-sliding.ts to reflect the following:

import { Directive, ElementRef, Renderer, Input } from '@angular/core';

@Directive({
  selector: '[animateItemSliding]'
})
export class AnimateItemSliding {

  @Input('animateItemSliding') shouldAnimate: boolean;

  constructor(public element: ElementRef, public renderer: Renderer) {

  }

  ngOnInit(){

    if(this.shouldAnimate){

      this.renderer.setElementClass(this.element.nativeElement, 'active-slide', true);
      this.renderer.setElementClass(this.element.nativeElement, 'active-options-right', true);

      // Wait to apply animation
      setTimeout(() => {
        this.renderer.setElementClass(this.element.nativeElement.firstElementChild, 'itemSlidingAnimation', true);
      }, 2000);

    }

  }

}

The first thing to notice is the use of [animateItemSliding] as the selector. You may be used to using a selector in other pages in your application that do not use the square brackets, so why do we need them? The selector works the same as CSS rules do. When creating pages, an element in the DOM is created with the name of the selector used, i.e:

<my-cool-page></my-cool-page>

If we wanted to grab that using CSS style selectors we would just have to use my-cool-page as the selector. The directive is applied as an attribute to an element though, it is not an element itself, so in order to grab it we need to use the CSS selector for selecting attributes, which looks like this:

[my-attribute]

We are also importing and making use of ElementRef and Renderer. By injecting ElementRef we are able to grab a reference to the element that the directive is attached to, which will allow us to modify its properties. Using Renderer here is not actually required, we could just use ElementRef to do what we need, but Renderer provides a more platform agnostic way to make changes, so it is better practice to use the renderer to make changes to the element.

We use @Input to grab the value that is being passed in through the attribute. Since we are using:

<ion-item-sliding [animateItemSliding]="shouldAnimate">

we will be able to grab the value for shouldAnimate, and then we use that to determine whether or not we run the code inside of ngOnInit.

Inside the ngOnInit function, which will trigger after the DOM has loaded (much like Ionic’s ionViewDidLoad lifecycle hook), we set a few classes on the sliding item element. Essentially, we are just copying what happens when the user manually slides an item.

We use the active-slide and active-options-right classes to make the options behind the sliding item visible, and then (after 2 seconds) we apply the itemSlidingAnimation class to the <button> inside of the sliding item. This will trigger the animation that we will create in the next step.

4. Create the Animation

The directive we created in the step above will handle applying the appropriate classes to the elements, but we still need to create the animation. When the <button> receives the itemSlidingAnimation class, it apply the styles of that class to the element. We will be using a transform to move the button to the left, just like what would happen when a user manually slide the item.

We will also be animating this though, rather than just instantly applying it, and then we will animate the button back to the closed position.

Modify src/app/app.scss to reflect the following:

@-webkit-keyframes slidingAnimation {
  0% {
    transform: translate3d(0,0,0);
    -webkit-transform: translate3d(0,0,0);
  }

  50% {
    transform: translate3d(-70px,0,0);
    -webkit-transform: translate3d(-70px,0,0);
  }

  100% {
    transform: translate3d(0,0,0);
    -webkit-transform: translate3d(0,0,0);
  }
}

.itemSlidingAnimation {
    -webkit-animation: slidingAnimation;
    animation: slidingAnimation;
    -webkit-animation-duration: 1000ms;
    animation-duraton: 1000ms;
}

This creates a keyframe animation that will move the button 70 pixels to the left (halfway through the animation), and then back to its starting position. You can modify this to change the speed or style of the animation if you like. If you are not familiar with keyframe animations in CSS, I have written a tutorial in the past that goes into a little more depth (it’s written for Ionic 1, but the concept is the same): How to Create Animations with CSS in Ionic.

5. Add the shouldAnimate Flag

There’s one more thing we need to do to finish this off. As you can see above, we are passing in shouldAnimate to the directive to determine whether or not to apply the animation. Now we need to set that flag in the home.ts file.

Modify src/pages/home.ts to reflect the following:

import { Component } from '@angular/core';

import { NavController } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

    shouldAnimate: boolean = true;

    constructor(public navCtrl: NavController) {

    }

}

For now, we just have the flag set to true, but you could easily change this dynamically to affect the behaviour of the animation.

Summary

If you take a look at your application now, you should now see this animation play 2 seconds after the page loads:

Ionic 2 Sliding Item Animation

Hopefully this tutorial highlights the power of directives (and this is just a simple example), and also the importance small user interface changes can have a big impact on user experience.

What to watch next...

  • Hi Josh, great post again 🙂

    I was wondering if its possible to do similar things with the Angular animation functionality or for this to work do you need to do the keyframe animation?

    • You should be able to achieve something similar with Angular animations, but I’m not sure if it would be as simple as this (i.e. being able to just use the directive wherever you want). I need to spend more time with Angular animations though.

      • I tried before to create reusable animations with Angular but struggled to get it going.

        Are their any performance differences between the keyframe animations using CSS and Angular’s animations?

      • All I know is that Angular animations use the Web Animations API, I couldn’t talk to performance differences between the two approaches.

      • I think I’m going to go with your approach in this article. Its a lot easier to work with and means I can apply things as a global style.

  • Luca Palezza

    Hi Josh,
    great posts! I have your book and it made me quick start with Ionic 2!
    I made a small change to your directive in order to avoid clicking problems on the button and remove the animation after the first one:

    ngOnInit(){

    if(this.shouldAnimate){
    this.renderer.setElementClass(this.element.nativeElement, ‘active-slide’, true);
    this.renderer.setElementClass(this.element.nativeElement, ‘active-options-right’, true);

    // Wait to apply animation
    setTimeout(() => {
    this.renderer.setElementClass(this.element.nativeElement.firstElementChild, ‘itemSlidingAnimation’, true);
    //remove animation for next renderings
    setTimeout(() => {
    this.renderer.setElementClass(this.element.nativeElement, ‘active-slide’, false);
    this.renderer.setElementClass(this.element.nativeElement, ‘active-options-right’, false);
    this.renderer.setElementClass(this.element.nativeElement.firstElementChild, ‘itemSlidingAnimation’, false);
    }, 1000);
    }, 1000);
    }

    Hope this helps.
    Bye,
    Luca

  • Francesco Benedetto

    In app.scss should be animation-duration: 1000ms; and not animation-duraton: 1000ms;

  • Đinh Thảo Phan

    Thank you for your great post. But i have an error when follow these steps: Can’t bind to ‘animateItemSliding’ since it isn’t a known property of ‘ion-item-sliding’. Can you help me please?

  • John

    What if i want to animate the first item in ngFor loop.

    how to tackle that?

    • J.R.

      E.g.
      Where 0 is the index for the first item, 1 for the second… this way you can animate every item you want