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:
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:
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.9.2
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
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"></ion-item-sliding>
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.
shouldAnimate
Flag
5. Add the 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:
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.