Using Gestures in Angular 2 with Typescript

Angular icon Angular icon

Created by Steve Schwarz / @steveaschwarz / tech.agilitynerd.com

Angular 2 Gesture Support

Built-in mappings for Hammer.js touch gesture events:

  • Rotate
  • Pinch
  • Press
  • Pan
  • Tap
  • Swipe

Does not unify mouse and gesture events.

HammerJS Setup

Install HammerJS and touch-action polyfill:

$ npm install hammerjs hammer-timejs

Add includes to app.module.ts so they'll be used/bundled:

import 'hammerjs';
import 'hammer-timejs';

Gesture Events

Each gesture defines multiple events:

pan, panstart, panmove, panend, pancancel, panleft, panright, panup, pandown

Bind desired events in template(s) and $event for use in handler:

<div class="thing-to-move"
 (panstart)="onPanStart($event)"
 (panmove)="onPan($event)"> Move Me! <div>

Gesture Event Handlers

Event handlers update component x, y properties:

  • Capture model property's initial values on eventstart.
  • Adjust model property value(s) by adding eventmove deltas to initial values.
x: number = 0;
y: number = 0;
startX: number = 0;
startY: number = 0;

onPanStart(event: any): void {
  event.preventDefault();
  this.startX = this.x;
  this.startY = this.y;
}

onPan(event: any): void {
  event.preventDefault();
  this.x = this.startX + event.deltaX;
  this.y = this.startY + event.deltaY;
}

Update Template to Move Element

Update element's position during gestures, use bound properties in template:

<div [style.marginLeft.px]="x" [style.marginTop.px]="y" ...>
  <div>Move Me!</div>
  <div>({{x}}, {{y}})</div>
</div>

Demo Component

import { Component } from '@angular/core';
@Component({
  selector: 'demo-one',
  styles: ['.demo-one {width:200px;height:200px;background-color: slateblue;color: #fff;}',
           '.demo-one:hover {cursor:pointer}'],
  template: `
{{title}}
({{x}}, {{y}})
` }) export class DemoOne { x: number = 0; y: number = 0; title = 'Drag Me!'; startX: number = 0; startY: number = 0; onPanStart(event: any): void { this.startX = this.x; this.startY = this.y; } onPan(event: any): void { event.preventDefault(); this.x = this.startX + event.deltaX; this.y = this.startY + event.deltaY; } }

Draggable Component

Loading...

Define Directives for Reuse

Put gesture code in a directive taking two inputs and outputing a locationChange event:


import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core';

@Directive({
  selector: '[dragmove]'
})
export class DragMoveDirective {
  @Input() x;
  @Input() y;
  @Output() locationChange = new EventEmitter<any>();

  startX = 0;
  startY = 0;

  @HostListener('panstart', ['$event']) protected onPanStart(event) {
    event.preventDefault();
    this.startX = this.x;
    this.startY = this.y;
  }

  @HostListener('panmove', ['$event']) protected onPanMove(event) {
    event.preventDefault();
    this.x = this.startX + event.deltaX;
    this.y = this.startY + event.deltaY;
    this.locationChange.emit({x: this.x, y: this.y});
  }
}

Update Our Component


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

@Component({
  selector: 'demo-two',
  styles: ['.demo-two {width:200px;height:200px;background-color: lightgreen;color: #fff;}',
           '.demo-two:hover {cursor:pointer}'],
  template: `
{{title}}
({{x}}, {{y}})
` }) export class DemoTwo { x: number = 50; y: number = 50; title = 'Drag Me!'; onPan(event: any): void { this.x = event.x; this.y = event.y; } }

Component With Drag Move Directive

Loading...

Tips

Use mouse/touch pad and shift key to emulate multi-finger gestures during development.


<script src="/hammerjs/touch-emulator.js"></script>
<script>TouchEmulator();></script>
						

Chrome Dev Tools can emulate gestures.

Mobile viewport settings can impact gestures (i.e. zoom):

<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

Tips

Vertical pan events can be delayed due to scroll handling. Consider:

							document.body.addEventListener('touchmove', function(event) {
  event.preventDefault();
}, false);
						

Disable default image drag:

							import { Directive, HostListener } from '@angular/core';
@Directive({
  selector: '[preventDefault]'
})
export class MouseDownPreventDefaultDirective {
  @HostListener('mousedown', ['$event']) protected onPMouseDown(event) {
      event.preventDefault();
  }
}

Resources