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
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