12

I was wondering if I could get some help in regards to events for mobile devices. I was looking around for a way to bind functions to swipe events in Angular 2. I saw in this this issue on Github that mentions that Angular 2 uses Hammer.js for mobile event handling.

I'm having some trouble getting the event to work because I get the following error:

EXCEPTION: Hammer.js is not loaded, can not bind swipeleft event

A snippet of my code is below:

import {Component, View, AfterContentInit} from 'angular2/core';
import {HelperService} from "./helper-service";
import {HammerGesturesPluginCommon} from 'angular2/src/platform/dom/events/hammer_common'

@View({
  template: `<div [id]="middleCircle" (swipeleft)="doThis()"></div>`
})

export class ColumnDirective implements AfterContentInit {
  constructor(private helperService:HelperService) {}
  doThis(){
     console.log('This thing has been done.');
   }
 }

If I add in Hammer Gestures to my constructor, I get this error:

constructor(private helperService:HelperService, private hammerGesturesPluginCommon: HammerGesturesPluginCommon) {}

EXCEPTION: No provider for t! (ColumnDirective -> t)

Any help with this issue would be appreciated!

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Eric Gonzalo
  • 301
  • 1
  • 4
  • 10
  • I just found this thread as I'm trying to implement the same thing. Will let you know if I find anything. – Billy Mayes Mar 01 '16 at 16:43
  • I was able to get past the "Hammer.js is not loaded" by adding a script tag for hammer.js to my index.html (I'm using the angular 2 seed project), however when I trigger a swipe I get now get a huge list of errors triggered starting with "EXCEPTION: RangeError: Maximum call stack size exceeded". – Billy Mayes Mar 01 '16 at 16:55
  • Yeah @BillyMayes same problem now. – Eric Gonzalo Mar 01 '16 at 16:59
  • Hey @EricGonzalo did you get this to work eventually? I'm not getting any events from Hammer with current Angular2 (master) – wannabeartist Mar 16 '16 at 10:54
  • 1
    @wannabeartist Yeah, following Billy Mayes' answer down below pretty much made it work out. Instead of using Angular2, using the hammer.js setup worked better. Though I had to place my hammer.js functions in a `setTimeout` because I setup my content in an `AfterContentInit`, I also didn't have a `hammerInitialized` check on my component. – Eric Gonzalo Mar 16 '16 at 14:35

6 Answers6

14

After a lot of fiddling, I had no success getting Angular2's HammerGesturesPluginCommon to work (so far). Inspired by Bill Mayes answer, I am submitting this as an elaboration of his answer which I was able to get working (tested on an Ipad mini and an Android phone).

Basically, my solution is as follows.

First, manually reference hammer.js in the script tags of your index.html file (I also reference hammer-time to eliminate the 300ms delay):

Second, install the Type Definitions for hammerjs (tsd install hammerjs -save). Then you can can create an Angular2 attibute directive like this:

/// <reference path="./../../../typings/hammerjs/hammerjs.d.ts" />
import {Directive, ElementRef, AfterViewInit, Output, EventEmitter} from 'angular2/core';
@Directive({
    selector: '[hammer-gestures]'
})
export class HammerGesturesDirective implements AfterViewInit {
    @Output() onGesture = new EventEmitter();
    static hammerInitialized = false;
    constructor(private el: ElementRef) {

    }
    ngAfterViewInit() {

        if (!HammerGesturesDirective.hammerInitialized) {

            let hammertime = new Hammer(this.el.nativeElement);
            hammertime.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
            hammertime.on("swipeup", (ev) => {
                this.onGesture.emit("swipeup");
            });
            hammertime.on("swipedown", (ev) => {
                this.onGesture.emit("swipedown");
            });
            hammertime.on("swipeleft", (ev) => {
                this.onGesture.emit("swipeleft");
            });
            hammertime.on("swiperight", (ev) => {
                this.onGesture.emit("swiperight");
            });
            hammertime.on("tap", (ev) => {
                this.onGesture.emit("tap");
            });

            HammerGesturesDirective.hammerInitialized = true;
        }


    }
}

You need to set Hammer.DIRECTION_ALL if you want to detect vertical (up/down) swipes (left/right swipes are default, not vertical). More options can be found about the hammer api here: http://hammerjs.github.io/api/#hammer

Finally, in your parent component you can do something like this:

import {Component} from "angular2/core";
import {HammerGesturesDirective} from "./path/HammerGesturesDirective";

    @Component({
        selector: "my-ng2-component",
        template: `<div style='width:100px; height: 100px;background-color: red' 
            (onGesture)="doSwipe($event)" hammer-gestures></div>`,
        directives: [HammerGesturesDirective]

    })
    export class MyNg2Component {
        constructor() { }

        doSwipe(direction: string) {
            alert(direction);
        }

    }

This way you only need to reference the attribute hammer-gestures when you want to enable hammer gestures on any particular element. Note: the element seems to need a unique id to work.

Nhan
  • 3,595
  • 6
  • 30
  • 38
brando
  • 8,215
  • 8
  • 40
  • 59
  • 2
    I've not tested this yet, but I like the idea so much i said "cool" out loud in the office. It looks clean – Sam Vloeberghs Jun 20 '16 at 09:11
  • well explained answer :) +1 – Pardeep Jain Jul 20 '16 at 11:19
  • I found that adding this directive to multiple elements in my component, due to the static `hammerInitialized` property, only the first element was wired up. I removed the check and it seems to work better for me. (out loud "cool" also.) – GaryB96 Oct 08 '16 at 20:33
  • 1
    In the latest version of Angular2, I don't think `directives` exists on `@Component`s anymore. I had to add the directive in `declarations` in my app's `@NgModule` instead. – twiz Dec 10 '16 at 03:32
  • @twiz Directive as a decorator still exits. As a property of a Component, you are right, it is no longer used – brando Dec 11 '16 at 04:50
  • Solution tested on an Android device (Ionic 2 framework), it works. – g3k0 Apr 18 '17 at 12:49
  • @brando.thanks for your soln.I am navigating from one component to anotehr while swiping.after navigation ,swipe is working after reload the page.pls help to fix it – kamalav Dec 04 '17 at 10:14
8

Well, long after the OP, but if you've only got simple requirements and don't want to muck with hammer.js, here's a basic horizontal swiper. Just drop into a component and add your own doSwipeLeft and doSwipeRight functions.

  touch1 = {x:0,y:0,time:0};

  @HostListener('touchstart', ['$event'])
  @HostListener('touchend', ['$event'])
  //@HostListener('touchmove', ['$event'])
  @HostListener('touchcancel', ['$event'])
  handleTouch(ev){
    var touch = ev.touches[0] || ev.changedTouches[0];
    if (ev.type === 'touchstart'){
      this.touch1.x = touch.pageX;
      this.touch1.y = touch.pageY;
      this.touch1.time = ev.timeStamp;
    } else if (ev.type === 'touchend'){
      var dx = touch.pageX - this.touch1.x;
      var dy = touch.pageY - this.touch1.y;
      var dt = ev.timeStamp - this.touch1.time;

      if (dt < 500){
        // swipe lasted less than 500 ms
        if (Math.abs(dx) > 60){
          // delta x is at least 60 pixels
          if (dx > 0){
            this.doSwipeLeft(ev);
          } else {
            this.doSwipeRight(ev);
          }
        }
      }
    } 
  }
Mike M
  • 4,358
  • 1
  • 28
  • 48
3

I was a bit hesitant to add swipe gestures to my application because the answers here seemed to suggest it would be a bit messy.

EXCEPTION: Hammer.js is not loaded, can not bind swipeleft event

This error is easily dealt with by simply loading hammer.js. No custom code needed.

The other error mentioned in the comments seems to have been a bug that is fixed as of rc1.

Kevin Stricker
  • 17,178
  • 5
  • 45
  • 71
2

I was able to get something working by bypassing the built-in Hammer integration and running my own:

import { Component, ElementRef, AfterViewInit } from 'angular2/core';

@Component({
  selector: 'redeem',
  templateUrl: './redeem/components/redeem.html'
})
export class RedeemCmp implements AfterViewInit {

    static hammerInitialized = false;

    constructor(private el:ElementRef) { }

    ngAfterViewInit() {
        console.log('in ngAfterViewInit');
        if (!RedeemCmp.hammerInitialized) {
            console.log('hammer not initialised');

            var myElement = document.getElementById('redeemwrap');
            var hammertime = new Hammer(myElement);
            hammertime.on('swiperight', function(ev) {
                console.log('caught swipe right');
                console.log(ev);
            });

            RedeemCmp.hammerInitialized = true;
        } else {            
            console.log('hammer already initialised');
        }
    }
}
Billy Mayes
  • 315
  • 3
  • 13
  • That seems like a much better idea. Since we're still in beta, lots of things are still quite buggy. Thanks for the help! – Eric Gonzalo Mar 01 '16 at 17:37
  • Using RC1 it says Hammer it giver me erors at line `var hammertime = new Hammer(myElement);` - Hammer not defined. Any idea what's changed in RC1 ? – LorDex May 12 '16 at 12:13
2

"EXCEPTION: Hammer.js is not loaded, can not bind swipeleft event" is raised because hammerjs needs to be included into page.

Example: <script src="node_modules/hammerjs/hammer.js"></script>

I have read all other answers about how to use hammerjs with angular and they look hacky. By default HammerJs has disabled SwipeDown and SwipeUp methods. All previous answers aren't right angular2 typescript way to solve gestures problem or override default settings of hammer. It took me about 4 hours of digging into angular2 and hammerjs commits.

Here you are:

First thing that we will need are hammerjs typings for angular2.

typings install github:DefinitelyTyped/DefinitelyTyped/hammerjs/hammerjs.d.ts#de8e80dfe5360fef44d00c41257d5ef37add000a --global --save

Next we need to create our custom overriding config class. This class can be used to override all default hammerjs and angular2 hammerjs config settings.

hammer.config.ts:

import { HammerGestureConfig } from '@angular/platform-browser';
import {HammerInstance} from "@angular/platform-browser/src/dom/events/hammer_gestures";


export class HammerConfig extends HammerGestureConfig  {

    buildHammer(element: HTMLElement): HammerInstance {
        var mc = new Hammer(element);

        mc.get('pinch').set({ enable: true });
        mc.get('rotate').set({ enable: true });

        mc.add( new Hammer.Swipe({ direction: Hammer.DIRECTION_ALL, threshold: 0 }) );

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

        return mc;
    }
}

Last thing that we need to do, is to open our main boostrap file and insert our custom config into boostrap method like this:

// ... other imports ...
import { HammerConfig } from './shared/hammer.config';

bootstrap(AppComponent, [
    provide(HAMMER_GESTURE_CONFIG, { useClass: HammerConfig })
]);

Now in our application we can use swipeDown and swipeUp

<div class="some-block" (swipeDown)="onSwipeDown"></div>

This pull request enabled overriding of default settings and gave me starting point to find right way:

https://github.com/angular/angular/pull/7924

user991
  • 1,339
  • 2
  • 24
  • 45
0

You need to add

HelperService and HammerGesturesPluginCommon to providers somewhere, either in the @Component(...) annotation or in bootstrap(AppComponent, [...]) to get rid of the error "No provider for ..."

Did you add a script tag for Hammer.js somewhere on your entry page?

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I've added a `providers: [HammerGesturesPluginCommon]` to my `@Component` and included Hammer.js in a script tag from a cdn, now I'm getting maximum call stack size exceeded, seems like we're closer! – Eric Gonzalo Mar 01 '16 at 16:59
  • Maybe https://github.com/angular/angular/issues/5964 or https://github.com/angular/angular/issues/5964 provide some information. From what I remember is to not explicitely add `zone.js` (I didn't run into this issue myself, just remember seeing the discussions and I don't know if this is still current). Could also be a problem with the order of the script tags. – Günter Zöchbauer Mar 01 '16 at 17:04