How to implement long press gestures in Ionic 4

Recently we had an app project in which a long-press was the perfect UX interaction.

As it turns out, it’s not as straightforward to implement in Ionic 4 as it is natively. But then everything else is, and so we ploughed on.

The event that we want to leverage here is very similar to the standard (click) event binding provided by angular, but for a long press, there are 2 actions we want to hook into, (press) and (pressup). You can try adding these events straightaway, but you will notice the following issue:The “press” event cannot be bound because Hammer.JS is not loaded and no custom loader has been specified.

Setup

As they say, nothing worth having comes easily, so we’ve got a little bit of configuration to do before we get to play with events.

Install HammerJs

So we want to make HammerJS available across the entire project by adding it via npm: npm install hammerjs — save

Next, we add it to our codebase by importing it directly within src/main.ts :

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

import "hammerjs"; // HAMMER TIME

if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

Setup a Hammer Gesture Config

Now we should be able to access Hammer directives inside our app, but with Ionic 4 being in beta, we ran into some issues at this point and found the resolution was to create our own HammerGestureConfig by extending it, so for your convenience, I’ve added how we did that below:

import { Injectable } from "@angular/core";
import { HammerGestureConfig } from "@angular/platform-browser";

/**
*
@hidden
* This class overrides the default Angular gesture config.
*/
@Injectable()
export class IonicGestureConfig extends HammerGestureConfig {
buildHammer(element: HTMLElement) {
const mc = new (<any>window).Hammer(element);

for (const eventName in this.overrides) {
if (eventName) {
mc.get(eventName).set(this.overrides[eventName]);
}
}

return mc;
}
}

Add to App Providers

With HammerJS added and a custom HammerGestureConfig defined, we want to add it to our Angular providers in app.module.ts.

@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
providers: [
StatusBar,
SplashScreen,
{provide: RouteReuseStrategy, useClass: IonicRouteStrategy},
{
provide: HAMMER_GESTURE_CONFIG,
useClass: IonicGestureConfig
},

],
bootstrap: [AppComponent]
})

The Good part

Now that the setup is out of the way, we can now get playing with the events.

For the sake of this demo, I’m going to use an ion-button, but you can use whatever HTML element you want.

Add Event Bindings

So go ahead and add a (press) and (pressup) event binding onto your element and point them to a respective function on your controller:

<--Template Segment-->
<ion-button (press)="onPress($event)" (pressup)="onPressUp($event)">Press Me</ion-button>

// Controller Functions
onPress($event) {
console.log("onPress", $event);
}

onPressUp($event) {
console.log("onPressUp", $event);
}

If you’ve set it all up correctly, you will see the events firing in your console. The onPress will fire after about 1 second of a long press, and upon releasing the onPressUp event will then fire.

What next?

Well, given that this UX interaction could be used for a gazillion different things, I’m not going to preempt why YOU need it, however, a good use case for this demo (I think) would be to create a progress counter whilst the button is being held down.

Demo: Long Press to Increment a Counter

First up, I’ve added the source code for this demo on Github, so feel free to use it as a reference.

Press it, press it good.

Don’t forget: Event Bindings only fire once!

It may feel counterintuitive, but despite holding the button, the actual press event is only fired once, and so if we wish for the counter to continually increment until the button is released we have to find a way to recall the incrementing function. In this demo, I will use a simple setTimeout function to do this.

Managing the Timeout Interval

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

@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {

public progress: number = 0;

// Interval function
protected interval: any;

onPress($event) {
console.log("onPress", $event);
this.startInterval();
}

onPressUp($event) {
console.log("onPressUp", $event);
this.stopInterval();
}

startInterval() {
const self = this;
this.interval = setInterval(function () {
self.progress = self.progress + 1;
}, 50);
}

stopInterval() {
clearInterval(this.interval);
}

}

So I’ll try and break down exactly what is happening here. When the (press) event binding is observed in the markup, the onPress($event) method runs. This method calls the startInterval function, which creates an interval function that is set to re-run every 50 milliseconds, and binds the function to a local variable so that it can be cleared (stopped) at a later time. Notice the redeclaration of this as another local variable self. Don’t forget the importance of the this keyword in javascript and how it’s context can change!

So, now that the interval is up and running, you will see that the progress variable is incrementing by 1 every 50 milliseconds. Now when the user lifts their finger off the button, the (pressup) event binding is fired, which in our example clears the interval that was created by the initial press event, and so the counter stops incrementing… magic!

Reuse and Scalability

One thing you might note is that if you wanted to have multiple buttons on the page, there would be an awful lot of code duplication. The smart thing to do in this case is to move all of that press logic into its own component and have it self-contained and reusable. But that’s for another day!

Source Code

Don’t forget, the source code for this demo is available at https://github.com/pjhartin/ionic4-long-press-demo