Leveraging mobile interactivity in Angular2

Seeking assistance with implementing swipe events on mobile devices for Angular 2. I came across a mention of using Hammer.js for mobile event handling in this Github issue.

Encountering an error when trying to bind the swipe event:

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

Here is a snippet of the code causing the issue:

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

When adding Hammer Gestures to the constructor, this error occurs:

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

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

Any assistance on resolving this issue would be greatly appreciated!

Answer №1

Despite numerous attempts, I struggled to make Angular2's HammerGesturesPluginCommon function properly. Taking inspiration from Bill Mayes' response, I am presenting this elaboration of his solution that I managed to implement successfully (tested on an iPad mini and an Android phone).

In essence, my approach is as follows.

Firstly, manually include hammer.js in the script tags of your index.html file (I also add hammer-time to remove the 300ms delay):

Secondly, install the Type Definitions for hammerjs (tsd install hammerjs -save). Now you can create an Angular2 attribute 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;
}


}
}

To detect vertical (up/down) swipes, you need to set Hammer.DIRECTION_ALL (left/right swipes are default, not vertical). More information about the Hammer API can be found here:

Lastly, in your parent component, you can do something similar to 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 approach allows you to simply reference the attribute hammer-gestures when enabling hammer gestures on a specific element. Note: the element must have a unique id to function correctly.

Answer №2

Although this response comes long after the original post, if you have simple requirements and prefer not to deal with hammer.js, here is a straightforward horizontal swiper. Simply add it to a component and include 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);
          }
        }
      }
    } 
  }

Answer №3

Initially, I was hesitant about incorporating swipe gestures into my application after reading some mixed opinions here.

ERROR: Unable to bind swipeleft event as Hammer.js is missing

This issue can be easily resolved by loading hammer.js without the need for any additional custom coding.

Furthermore, it appears that the bug mentioned in the comments has been rectified in the latest release candidate (rc1).

Answer №4

To solve the issue, I found a workaround by bypassing the default Hammer integration and setting up my own solution:

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('Executing ngAfterViewInit');
        if (!RedeemCmp.hammerInitialized) {
            console.log('Hammer not initialized');

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

            RedeemCmp.hammerInitialized = true;
        } else {            
            console.log('Hammer already initialized');
        }
    }
}

Answer №5

"ERROR: The swipeleft event cannot be bound because Hammer.js is missing from the page." This issue occurs when hammerjs is not included in the webpage.

For example, ensure to add the following script tag to include HammerJs: <script src="node_modules/hammerjs/hammer.js"></script>

I have reviewed various solutions on integrating hammerjs with Angular and found them to be unconventional. By default, HammerJs disables SwipeDown and SwipeUp methods. Previous approaches did not align with the recommended way of handling gestures in angular2 using typescript or customizing hammer's default settings. It took me approximately 4 hours of exploring angular2 and hammerjs repositories.

Here is a suggested approach:

Firstly, we need to install hammerjs typings for angular2.

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

Next step involves creating a custom configuration class to override default hammerjs and angular2 hammerjs configurations.

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

Finally, we need to integrate our custom config into the main bootstrap file as shown below:

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

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

With these changes, our application will now support both swipeDown and swipeUp gestures.

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

This enhancement allows for customization of default settings and guided me towards the correct approach:

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

Answer №6

To resolve the error "No provider for ...", make sure to include HelperService and HammerGesturesPluginCommon in the providers section. This can be done within the @Component(...) annotation or by adding them to bootstrap(AppComponent, [...]).

Additionally, ensure that you have included a script tag for Hammer.js on your entry page.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using ngModel instead of value to bind a custom Angular directive for currency input

Currently, I am using a specialized mat-input-currency format directive to automatically convert my field inputs into currency. You can find the npm repository here. However, the directive binds the element data to [value] of the input, and I require it t ...

The Angular error 'TypeError: property of undefined' is indicating that a property

I am working with an .html file where I utilize {{candidate.phone}} and can see the result on the page, but encounter an error in the console saying ERROR TypeError: Cannot read property 'phone' of undefined. My understanding is that this error o ...

Discover the potential of JavaScript's match object and unleash its power through

In the given data source, there is a key called 'isEdit' which has a boolean value. The column value in the data source matches the keys in the tempValues. After comparison, we check if the value of 'isEdit' from the data source is true ...

What are the best methods for protecting a soda?

My code is in strict mode, and I am encountering an issue with the following snippet: const a: string[] = []; // logic to populate `a` while (a.length > 0) { const i: string = a.pop(); // This line is causing an error console.log(i); // additio ...

Encountered Angular SSR Serve Error: NullInjectorError - StaticInjectorError in AppServerModule with the following reference:

While working on building an application with Angular's SSR and serving it, I encountered a specific error. All services and components have been properly injected. Error: ERROR Error [NullInjectorError]: StaticInjectorError(AppServerModule)[REQUEST] ...

NSwag.MSBuild encountering problems with TypeScript versions

While working in NSwag Studio, I came across a flag that allows the use of a specific TypeScript version for generating TypeScript typings/code. However, when attempting to achieve the same functionality within my .csproj file, the approach seems to have n ...

Obtaining data from a nested JSON using Angular4 and AngularFire2's db.list

I have a restaurant and I wanted to share a tip: Here is the JSON Structure: http://prntscr.com/gn5de8 Initially, I attempted to retrieve data from my restaurant as follows: <p *ngFor="let tip of restaurants[item.$key].tips" [innerHTML]=&qu ...

The power of negative multiplication in TypeScript and React

I am working with a state variable called sortDirection const [sortDirection, setSortDirection] = useState<1 | -1>(1); My goal is to allow a button to toggle the state variable like this setSortDirection(sortDirection * -1); However, I encounter a ...

A Model in TypeScript

{ "title": { "de-DE": "German", "fr-FR": "French", "en-CA": "English" }, "image": "/tile.jpg", "url": "/url/to/version" } After receiving this JSON data, my model structure is as follows: export class MyModelStruct ...

Receiving errors in React/TS/material-ui when attempting to use a variable as a value for a grid property. Messages include "No overload matches" and "Type 'number' is not assignable to type..."

tl;dr: When using a variable as the value of a grid xs property in JSX, material-ui throws a TS error. I'm working on implementing grids in material-ui with React/TypeScript. The goal is to make the width of a specific element dependent on the quant ...

The immutability of the List in JavaScript and its

I am confused about how the size of an Immutable JS List grows. According to the official documentation example at https://facebook.github.io/immutable-js/docs/#/List/push, pushing something into an immutable js List should automatically increase its size ...

WebStorm is failing to identify globally scoped external libraries

I'm currently working on a project using AngularJS (1.6.5) in WebStorm. The issue I'm encountering is that WebStorm isn't recognizing the global variables that AngularJS defines. I've made sure to install AngularJS and the correct @type ...

Creating a new formGroup and submitting it with model-driven form in Angular 2

Adding New Entries to FormArray Using input Field If I want to add values to an existing array through a form input, I can utilize the (click)="addAddress()" in the HTML file and define addAddress in the component.ts to update the values in an array withi ...

Alert once all observables have finished

I have a collection of observables, each representing a file that needs to be uploaded using an HTTP request. My objectives are: To emit an event for each successfully uploaded file, which will be handled in the parent component. To notify when all file ...

Using a try block inside another try block to handle various errors is a common practice in JavaScript

In an effort to efficiently debug my code and identify the location of errors, I have implemented a try-catch within a try block. Here is a snippet of the code: for (const searchUrl of savedSearchUrls) { console.log("here"); // function will get ...

What is the best way to toggle the Angular date picker on and off for a specific date with Angular Material?

I need to display the start date and end date where the start date is in format dd/mm/yyyy, for example 10/09/2020, and the end date should be yesterday's date, i.e., 09/09/2020. All other dates should be disabled. What steps should I take to impleme ...

The Angular Material date picker's keyboard input format is functional only in Chrome

I'm facing a challenge with the date picker component from Angular Material. When I try to manually type in a date like "2019.12.20" instead of selecting it, the input only works in Google Chrome. But when I tested it on Edge and Firefox, the date val ...

Rotating carousel powered by object data

Here is a carousel that I have set up to display images from an object called pictures: <div class="carousel-inner mb-5"> <div *ngFor="let pic of pictures; let i = index"> < ...

Loading CSS files conditionally in Angular2's index.html

Currently, my index.html page features a dark theme: <base href="/"> <html> <head> <title>XXX</title> </head> <body> <link rel="stylesheet" type="text/css" href="assets/dark_room.css"> <my-app ...

Error: No provider found for _HttpClient in the NullInjector context

Hello everyone, I am new to Angular and currently facing an issue that has me stuck. The error message I'm receiving is as follows: ERROR NullInjectorError: R3InjectorError(Standalone[_AppComponent])[_ApiCallServiceService -> _ApiCallServiceService ...