Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
onLongPress AngularJS Directive - Great for mobile!
// Somewhere in your controllers for this given example
// Example functions
$scope.itemOnLongPress = function(id) {
console.log('Long press');
}
$scope.itemOnTouchEnd = function(id) {
console.log('Touch end');
}
// Add this directive where you keep your directives
.directive('onLongPress', function($timeout) {
return {
restrict: 'A',
link: function($scope, $elm, $attrs) {
$elm.bind('touchstart', function(evt) {
// Locally scoped variable that will keep track of the long press
$scope.longPress = true;
// We'll set a timeout for 600 ms for a long press
$timeout(function() {
if ($scope.longPress) {
// If the touchend event hasn't fired,
// apply the function given in on the element's on-long-press attribute
$scope.$apply(function() {
$scope.$eval($attrs.onLongPress)
});
}
}, 600);
});
$elm.bind('touchend', function(evt) {
// Prevent the onLongPress event from firing
$scope.longPress = false;
// If there is an on-touch-end function attached to this element, apply it
if ($attrs.onTouchEnd) {
$scope.$apply(function() {
$scope.$eval($attrs.onTouchEnd)
});
}
});
}
};
})
<ion-item ng-repeat="item in list" on-long-press="itemOnLongPress(item.id)" on-touch-end="itemOnTouchEnd(item.id)">
{{ item }}
</ion-item>
@micfok

This comment has been minimized.

Copy link

micfok commented Jul 31, 2014

Great directive! Surprised Angular doesn't have something native.

@wbprice

This comment has been minimized.

Copy link

wbprice commented Aug 10, 2014

Do you have this running in a jsfiddle somewhere? I'm having some trouble getting this working in my project.

@diogomachado

This comment has been minimized.

Copy link

diogomachado commented Aug 26, 2014

Good job, I was looking for this solution!

@gpowork

This comment has been minimized.

Copy link

gpowork commented Sep 11, 2014

Good job! Really thank for this directive!

@joaodallarosa

This comment has been minimized.

Copy link

joaodallarosa commented Sep 20, 2014

Awesome! Thank you!

@bennekrouf

This comment has been minimized.

Copy link

bennekrouf commented Sep 25, 2014

Is there something equivalent for mouse ?

Inspired by https://github.com/pisi/Longclick

@urscion

This comment has been minimized.

Copy link

urscion commented Jan 4, 2015

Excellent, easy-to-implement hack for angularjs. Works great!

@eMatsiyana

This comment has been minimized.

Copy link

eMatsiyana commented Jan 6, 2015

No Demo or Fiddle?
I also can't get this to work...

@nespapu

This comment has been minimized.

Copy link

nespapu commented Feb 17, 2015

work like a charm! thank u!

@michaeloryl

This comment has been minimized.

Copy link

michaeloryl commented Mar 4, 2015

Here is a working Plunk. I updated it to display messages on-screen instead of using the Console. Bear in mind that this won't work with mouse clicks.... You'll need to look at it on a mobile device or a proper emulator.

http://plnkr.co/edit/Glt3h1?p=preview

@ejmarino

This comment has been minimized.

Copy link

ejmarino commented May 13, 2015

@bennekrouf to make it work with mouse clicks replace 'touchstart' for 'mousedown' and 'touchend' for 'mouse up'
@michaeloryl your plunker needs to add "track by $index" on ng-repeat message's section.

@vargavince91

This comment has been minimized.

Copy link

vargavince91 commented May 19, 2015

Really good directive, thanks!

Do any of you know how to prevent the default long-press behaviour on iOS (both in Safari and Chrome)? For example I added the on-long-press attribute to an image that should open a modal. When I long press it, the modal is opened, but I also got the "default" behaviour of the iOS browser (on Safari: Save Image/Copy).

If I set the $timeout's time (in line 19) to, let's say, 300 milliseconds, the function set by the long-press directive is called before the default, but I have to be very careful that I don't leave my finger on the screen any longer.

Edit:
Other workaround

@vdegenne

This comment has been minimized.

Copy link

vdegenne commented May 19, 2015

wow! that looks very neat, however isn't eval a bit risky ?

@oxsav

This comment has been minimized.

Copy link

oxsav commented Jun 19, 2015

.directive('onLongPress', function ($timeout, $parse) {

    return {
        restrict: 'A',
        link: function ($scope, $elm, $attrs) {
            $elm.bind('touchstart', function (evt) {
                // Locally scoped variable that will keep track of the long press
                $scope.longPress = true;
                var functionHandler = $parse($attrs.onLongPress);
                // We'll set a timeout for 600 ms for a long press
                $timeout(function () {
                    if ($scope.longPress) {
                        // If the touchend event hasn't fired,
                        // apply the function given in on the element's on-long-press attribute
                        $scope.$apply(function () {
                            functionHandler($scope, {$event: evt});
                        });
                    }
                }, 600);
            });

            $elm.bind('touchend', function (evt) {
                // Prevent the onLongPress event from firing
                $scope.longPress = false;
                var functionHandler = $parse($attrs.onLongPress);
                // If there is an on-touch-end function attached to this element, apply it
                if ($attrs.onTouchEnd) {
                    $scope.$apply(function () {
                        functionHandler($scope, {$event: evt});
                    });
                }
            });
        }
    };
});

Modify a little bit the directive to pass the event to the controller. Used $parse instead of $eval. In my case it was necessary.

@alexanderkoumis

This comment has been minimized.

Copy link

alexanderkoumis commented Oct 10, 2015

@oxsav thanks for your alternative method, just want to point out typo for those stumbling upon this in the future, in the touchend event binding functionHandler should instead be assigned to onTouchEnd as follows:

var functionHandler = $parse($attrs.onTouchEnd);
@jkufner

This comment has been minimized.

Copy link

jkufner commented Oct 13, 2015

It triggers also when moving the page with a finger. Touchmove event must be handled too.

@nrempel

This comment has been minimized.

Copy link

nrempel commented Oct 18, 2015

I found it helpful to change:

$scope.$apply(function() {
  $scope.$eval($attrs.onLongPress);
});

to:

$scope.$apply(function() {
  #scope.$event = event;
  $scope.$eval($attrs.onLongPress);
});

So you can work with the event in the controller.

@PariasDev

This comment has been minimized.

Copy link

PariasDev commented Nov 9, 2015

Thank you for your code. It has helped me tremendously.

But I wanted to mention one thing.

By integrating the code into a project with ionic, I noticed that if you touched repeatedly on the screen of the device, this triggering the "onLongPress" event.

To prevent that from happening. I made a small modification. Introducing the "$ timeout" into a variable to cancel it if you stop pressing the screen.

I hope you find useful this modification.

Thanks again.

// Add this directive where you keep your directives
.directive('onLongPress', function($timeout) {
    //Global variable, to cancel timer on touchend.
    var timer;

    return {
        restrict: 'A',
        link: function($scope, $elm, $attrs) {
            $elm.bind('touchstart', function(evt) {
                // Locally scoped variable that will keep track of the long press
                $scope.longPress = true;

                // We'll set a timeout for 600 ms for a long press
                timer = $timeout(function() {
                    if ($scope.longPress) {
                        // If the touchend event hasn't fired,
                        // apply the function given in on the element's on-long-press attribute
                        $scope.$apply(function() {
                            $scope.$eval($attrs.onLongPress)
                        });
                    }
                }, 600);
                timer;
            });

            $elm.bind('touchend', function(evt) {
                // Prevent the onLongPress event from firing
                $scope.longPress = false;

                // Prevent on quick presses, unwanted onLongPress selection.
                $timeout.cancel(timer);

                // If there is an on-touch-end function attached to this element, apply it
                if ($attrs.onTouchEnd) {
                    $scope.$apply(function() {
                        $scope.$eval($attrs.onTouchEnd)
                    });
                }
            });
        }
    };
})
@mbaer3000

This comment has been minimized.

Copy link

mbaer3000 commented Nov 16, 2015

Hi there. Great snippet to get going. However, two points: Keeping track of state via $scope.longPress is entirely unnessessary (and in fact buggy, as the previous commenter rightly notes). It suffices to simply cancel the $timeout-delayed function invocation. Also, using $scope.$apply is redundant. Which leaves us with a no-frills link function like:

  link: function(scope, elem, attrs) {
    var timeoutHandler;

    elem.bind('touchstart', function() {
      timeoutHandler = $timeout(function() {
        scope.$eval(attrs.longPress);
      }, 600);
    });

    elem.bind('touchend', function() {
      $timeout.cancel(timeoutHandler);
    });
  }
@GabrielGil

This comment has been minimized.

Copy link

GabrielGil commented Nov 28, 2015

Super dope!

Just a small typo on @mbaer3000:

elem.bind('touchstart', function() {
    timeoutHandler = $timeout(function() {
        scope.$eval(attrs.onLongPress); // onLongPress instead longPress :D
    }, 600);
});
@kaushik-evani

This comment has been minimized.

Copy link

kaushik-evani commented Dec 11, 2015

@vargavince91 Your answer for dealing with default long-press behaviour really helped. Saved me a some long hours. Cheers!

@kaushik-evani

This comment has been minimized.

Copy link

kaushik-evani commented Jan 8, 2016

@ejmarino Brilliant! Hey just a small typo. For mouse clicks the 'touchend' is replaced with 'mouseup'. Not 'mouse up'. I'm sure more experienced programmers than me would know this, but me not being one, spent around half hour trying to figure out what was wrong. But great tip for including mouse clicks. This is exactly what I was looking for. I wanted long press on the mobile website and long click on the desktop version! Cheers!!!

@PashaSk

This comment has been minimized.

Copy link

PashaSk commented Jan 19, 2016

Hey! I found this helpful for my feature with dialpad, and I need get "0" in case of click, and "+" in longpress
image

There was some issue: if user clicks zero button twice - first time short and second long, he'd get "0++". So I thought some time and past onStage variable to prevent firing of longPress event after "fastPress" was fired:

function DirectiveLongpress($timeout) {
return {
restrict: 'A',
link: function($scope, $elm, $attrs) {
//customized for mouse purpose

        var onStage = false;
        var timeoutHandler;

        $elm.bind('mousedown', function(evt) {
            if (onStage) return false;
            onStage = true;
            // Locally scoped variable that will keep track of the long press
            $scope.longPress = true;

            // We'll set a timeout for 600 ms for a long press
            timeoutHandler = $timeout(function() {
                if ($scope.longPress && onStage) {
                    // If the touchend event hasn't fired,
                    // apply the function given in on the element's on-long-press attribute
                    $scope.$apply(function() {
                        $scope.$eval($attrs.onLongPress);
                        onStage = false;
                    });
                }
            }, 600);
        });

        $elm.bind('mouseup', function(evt) {
            // Prevent the onLongPress event from firing
            $scope.longPress = false;
            // If there is an on-touch-end function attached to this element, apply it
            if ($attrs.onTouchEnd) {
                $scope.$apply(function() {
                    $scope.$eval($attrs.onTouchEnd);
                    onStage = false;
                });
            }
        });
    }
};

}

@puneethrai

This comment has been minimized.

Copy link

puneethrai commented Mar 25, 2016

If any one is still looking for this solution
@bennekrouf I've even solved the mouse event as well

Project Page: http://puneethrai.github.io/angular-long-press/

Installation and usage link in the page

@reduardo7

This comment has been minimized.

Copy link

reduardo7 commented Apr 5, 2016

I have other solution, implementing the "on-click" "on-touch" "on-touch-end" "on-long-press" and "time-out" attributes: https://jsfiddle.net/reduardo7/u47ok38e/

@serdarde

This comment has been minimized.

Copy link

serdarde commented Apr 6, 2016

Awesome thanks!!

@negesti

This comment has been minimized.

Copy link

negesti commented Jun 2, 2016

After trying to get this to work on mobile safari without "killing" scrolling for 2 hours, i have to thank you and post my solution.

css class
create a css class, that will prevent text selection on mobile devices.
NOTE if you have the onLongPress on a div, that contains input elements, the text inside the input will be selected even with this css

.unselectable {
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
}  

The directive

      function ngLongTouch($timeout, $parse) {
        return {
          restrict: 'A',
          link: function(scope, elem, attrs) {
            var timeoutHandler;
            var fn = $parse(attrs.ngLongTouch);

            // disable text selection
            elem.unselectable = "on";
            elem.addClass("unselectable");

            elem.bind('touchstart', function(event) {
              scope.longTouchTriggered = false;
              timeoutHandler = $timeout(function() {
                scope.longTouchTriggered = true;
                fn(scope, { $event:event });

              }, 600);
            });

            elem.bind('touchend', function() {
              if (scope.longTouchTriggered) {
                event.preventDefault();  
              }
              $timeout.cancel(timeoutHandler);
            });
          }
        }
      };
  }
};

To prevent normal ng-click events from firing, i added scope.longPressTriggered that is checked in touchend and calls preventDefault(). I needed this for two cases

  • a normal ng-click event on my div was called after the ngLongTouch
  • on long touch i open a modal and the buttons inside the modal are "pressed" on touchend
@fatemab

This comment has been minimized.

Copy link

fatemab commented Jun 28, 2016

Great directive but it does not work on window's phone. can you help with this?

@fatemab

This comment has been minimized.

Copy link

fatemab commented Jun 28, 2016

I got solution for window's phone.. You have to use "pointerdown" and "pointerup" events for windows.
posting code if any one needs:
app.directive('onLongPress', function ($timeout) {
return {
restrict: 'A',
link: function ($scope, $elm, $attrs) {

        var onlongtouch;
        var timer;
        var touchduration = 500;

        onlongtouch = function () {
            $scope.$apply(function () {
                $scope.$eval($attrs.onLongPress);
            });
        };

        touchstart = function () {
            timer = setTimeout(onlongtouch, touchduration);
        };

        touchend = function () {
            //stops short touches from firing the event
            if (timer)
                clearTimeout(timer); // clearTimeout;
        };


        ang_element = angular.element($elm);
        raw_element = ang_element[0];
        raw_element.addEventListener('pointerdown', touchstart);
        raw_element.addEventListener('pointerup', touchend);
        raw_element.addEventListener('touchstart', touchstart);
        raw_element.addEventListener('touchend', touchend);
    }
};

});

@persianturtle

This comment has been minimized.

Copy link

persianturtle commented Jul 13, 2016

When using this directive in combination with ngClick, I encountered a problem on the iPad. On the iPad, a touch even occurs after the long press directive executed the function with $eval. The solution is to just just preventDefault() to prevent this behavior, but this would stop normal ngClick behavior. To make this directive usable on an element with ngClick, I've added logic to only preventDefault() for long presses.

link: function(scope, element, attributes) {
  var timeoutHandler;

  element.bind('mousedown touchstart', function() {
    scope.start = Date.now();
    timeoutHandler = $timeout(function() {
      scope.$eval(attributes.onLongPress);
    }, 600);
  });

  element.bind('mouseup touchend', function(e) {
    scope.end = Date.now();
    if (scope.end - scope.start >= 600) {
      e.preventDefault();
    }
    $timeout.cancel(timeoutHandler);
  });
}
@marcramser

This comment has been minimized.

Copy link

marcramser commented Jul 17, 2016

Thanks a lot! Save me much time.

@raviji

This comment has been minimized.

Copy link

raviji commented Sep 17, 2016

For me not working on ipad guys, please help me out

@Vespira

This comment has been minimized.

Copy link

Vespira commented Sep 19, 2016

Thanks I took this code and adapted it, it works well. I'm just not sure it's a good thing to manually "$eval" methods. I've heard that normally you should always let angular eval automatically the things at each cycle.

@3Nex

This comment has been minimized.

Copy link

3Nex commented Sep 26, 2016

I've found that the long press event fires even if you just grab on that element to scroll down the page. So I've added the following touchmove event to cancel the long press from firing, and prevent that behavior:

$elm.bind("touchmove", function(evt) {
    $scope.longPress = false;
});

or for @mbaer3000's solution:

elem.bind("touchmove", function(evt) {
    $timeout.cancel(timeoutHandler);
});
@bharathidhasanS

This comment has been minimized.

Copy link

bharathidhasanS commented May 31, 2017

touchmove event not working as expected. Any help

@bhuvan-socialcurry

This comment has been minimized.

Copy link

bhuvan-socialcurry commented Jul 27, 2018

i need to select multiple elements on long press can somebody help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.