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!

5
  • I just found this thread as I'm trying to implement the same thing. Will let you know if I find anything. Commented Mar 1, 2016 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". Commented Mar 1, 2016 at 16:55
  • Yeah @BillyMayes same problem now. Commented Mar 1, 2016 at 16:59
  • Hey @EricGonzalo did you get this to work eventually? I'm not getting any events from Hammer with current Angular2 (master) Commented Mar 16, 2016 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. Commented Mar 16, 2016 at 14:35

6 Answers 6

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.

7
  • 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 Commented Jun 20, 2016 at 9:11
  • well explained answer :) +1 Commented Jul 20, 2016 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
    Commented Oct 8, 2016 at 20:33
  • 1
    In the latest version of Angular2, I don't think directives exists on @Components anymore. I had to add the directive in declarations in my app's @NgModule instead.
    – twiz
    Commented Dec 10, 2016 at 3:32
  • @twiz Directive as a decorator still exits. As a property of a Component, you are right, it is no longer used
    – brando
    Commented Dec 11, 2016 at 4:50
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);
          }
        }
      }
    } 
  }
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.

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');
        }
    }
}
2
  • That seems like a much better idea. Since we're still in beta, lots of things are still quite buggy. Thanks for the help! Commented Mar 1, 2016 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
    Commented May 12, 2016 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

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?

2
  • 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! Commented Mar 1, 2016 at 16:59
  • Maybe github.com/angular/angular/issues/5964 or 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. Commented Mar 1, 2016 at 17:04

Not the answer you're looking for? Browse other questions tagged or ask your own question.