Using mobile events in Angular2

2020-02-09 09:14发布

问题:

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!

回答1:

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.



回答2:

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.



回答4:

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');
        }
    }
}


回答5:

"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



回答6:

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?