Ionic 5 - long press gesture example

When I upgraded my project to Ionic 5 I had problems with hammerjs, so I writed this directive using GestureController, I hope is usefull for someone.

import {
    Directive,
    ElementRef,
    Input,
    AfterViewInit,
    NgZone,
    EventEmitter,
    Output
} from "@angular/core";
import { GestureController } from "@ionic/angular";

@Directive({
    selector: "[long-press]"
})
export class LongPressDirective implements AfterViewInit {

    @Output() press = new EventEmitter();
    @Input("delay") delay =  1500;
    action: any; //not stacking actions

    private longPressActive = false;

    constructor(
                    private el: ElementRef, 
                    private gestureCtrl: GestureController, 
                    private zone: NgZone
                ) {}
    
    ngAfterViewInit() {
        this.loadLongPressOnElement();
    }    

    loadLongPressOnElement() {
        const gesture = this.gestureCtrl.create({
            el: this.el.nativeElement,
            threshold: 0,
            gestureName: 'long-press',          
            onStart: ev => {                                       
                this.longPressActive = true;
                this.longPressAction();
            },
            onEnd: ev => {                
                this.longPressActive = false;
            }
        });
        gesture.enable(true);
    }
    
    private longPressAction() {
        if (this.action) {
            clearInterval(this.action);
        }
        this.action = setTimeout(() => {
            this.zone.run(() => {
                if (this.longPressActive === true) {
                    this.longPressActive = false;
                    this.press.emit();
                }
            });
        }, this.delay);
    }
}
<ion-button long-press (press)="yourAction()"></ion-button>
15 Likes

In Angular 9, we can simply do like below.

In Angular 9 it was decided that the implementation of Hammerjs was optional, so now we have to import the HammerModule from @angular/platform-browser .

Solution

Add the HammerModule import from @angular/platform-browser and add it to our @NgModule imports in the app.module.ts

import { BrowserModule, HammerGestureConfig, HammerModule, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';

// custom configuration Hammerjs
@Injectable()
export class HammerConfig extends HammerGestureConfig {
  overrides = <any> {
      // I will only use the swap gesture so 
      // I will deactivate the others to avoid overlaps
      'pinch': { enable: false },
      'rotate': { enable: false }
  }
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    HammerModule,
    .
    .
    .
  ],
  providers: [{provide: LocationStrategy, useClass: PathLocationStrategy},{
    provide: HAMMER_GESTURE_CONFIG,
    useClass: HammerConfig
  }],
5 Likes

This works for me. Thanks you.

1 Like

Thank you. Works for me.

2 years later you saved me!!
I had built a directive with the same purpose, but using @HostListeners for touch events.
Somehow the framework or Android, IKN was stoping the “touch”… Causing my press to hold no longer than 200ms.

You da real MVP!

Will post my problematic diretive:
Just in case, someone, someday know how to fix it… or at least explain…

import { Directive, HostListener, EventEmitter, Output, Input } from '@angular/core';
import { interval, Observable, Subject } from 'rxjs';
import { takeUntil, tap, filter } from 'rxjs/operators';

@Directive({
    selector: '[holdable]'
})
export class HoldableDirective {

    /**
     * HOW TO USE
     * 
     * <div holdable [holdTime]="500" (onHold)="holdHandler(anything)"></div> 
     */

    //Optional, without it the Emit() will be fired every 100ms
    @Input() holdTime: number; 
    @Output() onHold: EventEmitter<number> = new EventEmitter();
    state: Subject<string> = new Subject();
    cancel: Observable<string>;

    constructor() { 
        this.cancel = this.state
            .pipe(
                filter(v => v === 'cancel'),
                // tap(v => this.onHold.emit(0))
            );
    }

    @HostListener('touchend', ['$event'])
    @HostListener('touchcancel', ['$event'])
    @HostListener('touchmove', ['$event'])
    @HostListener('mouseup', ['$event'])
    @HostListener('mouseleave', ['$event'])
    @HostListener('mousemove', ['$event'])
    onExit() {
        this.state.next('cancel');
    }

    @HostListener('mousedown', ['$event'])
    @HostListener('touchstart', ['$event'])
    startHold($event){
        this.state.next('start');
        const n = 100;
        interval(n)
            .pipe(
                takeUntil(this.cancel),
                tap(v => {
                    if(this.holdTime){
                        if(v * n >= this.holdTime){
                            this.onHold.emit($event);
                            this.state.next('cancel');
                        }
                    } else {
                        this.onHold.emit($event);
                    }
                })
            )
            .subscribe();
    }
}

May be it’s usefull to prevent long-press while scrolling. Here is example:

loadLongPressOnElement() {
const gesture = this.gestureCtrl.create({
el: this.el.nativeElement,
threshold: 0,
gestureName: ‘long-press’,
onMove: ev => {
clearInterval(this.action);
this.longPressActive = false;
},
onStart: ev => {
this.longPressActive = true;
this.longPressAction();
},
onEnd: ev => {
this.longPressActive = false;
}
});
gesture.enable(true);
}

I improved the directive to include a tap (opposite of a long press). It also checks if the user dragged their finger, cancelling the “press” (because it’s a “drag” now).

Here is the directive and example:

import {
    Directive,
    ElementRef,
    Input,
    AfterViewInit,
    NgZone,
    EventEmitter,
    Output
} from "@angular/core";
import { GestureController } from "@ionic/angular";

@Directive({
    selector: "[long-press]"
})
export class LongPressDirective implements AfterViewInit {

    @Output() tap = new EventEmitter();
    @Output() press = new EventEmitter();
    @Input("delay") delay = 300;
    action: any; //not stacking actions

    private positions = {
        start: {
            x: undefined as number,
            y: undefined as number
        },
        current: {
            x: undefined as number,
            y: undefined as number,
        }
    }

    private longPressActive = false;

    constructor(
        private el: ElementRef,
        private gestureCtrl: GestureController,
        private zone: NgZone
    ) { }

    ngAfterViewInit() {
        this.loadLongPressOnElement();
    }

    loadLongPressOnElement() {
        const gesture = this.gestureCtrl.create({
            el: this.el.nativeElement,
            threshold: 0,
            gestureName: 'long-press',
            onStart: ev => {
                this.longPressActive = true;
                this.longPressAction();

                this.positions = {
                    start: { x: ev.startX, y: ev.startY },
                    current: { x: ev.currentX, y: ev.currentY }
                };

            },
            onMove: ev => {
                this.positions.current = { x: ev.currentX, y: ev.currentY };
            },
            onEnd: ev => {
                this.longPressActive = false;
            }
        });
        gesture.enable(true);
    }

    private longPressAction() {
        if (this.action) {
            clearInterval(this.action);
        }
        this.action = setTimeout(() => {
            this.zone.run(() => {
                // Check distance
                const xDistance = Math.abs(this.positions.start.x - this.positions.current.x);
                const yDistance = Math.abs(this.positions.start.y - this.positions.current.y);
                if(xDistance > 15 || yDistance > 15)
                    // User dragged finger
                    return;

                if (this.longPressActive === true) {
                    this.longPressActive = false;
                    this.press.emit();
                } else {
                    this.tap.emit();
                }
            });
        }, this.delay);
    }
}
<ion-button long-press (press)="onLongPress()" (tap)="onTap()"></ion-button>

My iteration was built on your shoulders marcelomoraesaguiar. Thank you for posting.

1 Like

Hi, you code works for me, but you know how prevents the keyboard close when long press a element html?. i tried to focus an input but the keyboard keep closing