Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix(ngTouch): deprecate ngClick and disable it by default
Browse files Browse the repository at this point in the history
This commit deprecates the ngClick directive from the ngTouch module.
Additionally, it disables it by default. It can be enabled in the new $touchProvider with
the $touchProvider.ngClickOverrideEnabled() method.

The directive was conceived to remove the 300ms delay
for click events on mobile browsers, by sending a synthetic click event on touchstart.
It also tried to make sure that the original click event that the browser sends after 300ms
was "busted", so that no redundant "ghost-clicks" appear.

There are various reasons why the directive is being deprecated.

- "This is an ugly, terrible hack!" (says so in the source)
- It is plagued by various bugs that are hard to fix / test for all platforms (see below)
- Simply including ngTouch activates the ngClick override, which means even if you simply want
to use ngSwipe, you may break parts of your app
- There exist alternatives for removing the 300ms delay, that can be used very well with Angular:
[FastClick](https://github.com/ftlabs/fastclick), [Tappy!](https://github.com/filamentgroup/tappy/)
(There's also hammer.js for touch events / gestures)
- The 300ms delay itself is on the way out - Chrome and Firefox for Android remove the 300ms delay
when the usual `<meta name="viewport" content="width=device-width">` is set. In IE, the
`touch-action` css property can be set to `none` or `manipulation` to remove the delay. Finally,
since iOs 8, Safari doesn't delay "slow" taps anymore. There are some caveats though, which can be
found in this excellent article on which this summary is based: http://developer.telerik.com/featured/300-ms-click-delay-ios-8/

Note that this change does not affect the `ngSwipe` directive.

Issues with interactive elements (input, a etc.) when parent element has ngClick:
Closes #4030
Closes #5307
Closes #6001
Closes #6432
Closes #7231
Closes #11358
Closes #12082
Closes #12153
Closes #12392
Closes #12545
Closes #12867
Closes #13213
Closes #13558

Other issues:
- incorrect event order
- incorrect event propagation
- ghost-clicks / failing clickbusting with corner cases
- browser specific bugs
- et al.

Closes #3296
Closes #3347
Closes #3447
Closes #3999
Closes #4428
Closes #6251
Closes #6330
Closes #7134
Closes #7935
Closes #9724
Closes #9744
Closes #9872
Closes #10211
Closes #10366
Closes #10918
Closes #11197
Closes #11261
Closes #11342
Closes #11577
Closes #12150
Closes #12317
Closes #12455
Closes #12734
Closes #13122
Closes #13272
Closes #13447

BREAKING CHANGE:

The `ngClick` override directive from the `ngTouch` module is **deprecated and disabled by default**.
This means that on touch-based devices, users might now experience a 300ms delay before a click event is fired.

If you rely on this directive, you can still enable it with the `$touchProvider.ngClickOverrideEnabled()`method:

```js
angular.module('myApp').config(function($touchProvider) {
  $touchProvider.ngClickOverrideEnabled(true);
});
```

For migration, we recommend using [FastClick](https://github.com/ftlabs/fastclick).
Also note that modern browsers remove the 300ms delay under some circumstances:
- Chrome and Firefox for Android remove the 300ms delay when the well-known `<meta name="viewport" content="width=device-width">` is set
- Internet Explorer removes the delay when  `touch-action` css property is set to `none` or `manipulation`
- Since iOs 8, Safari removes the delay on so-called "slow taps"

See this [article by Telerik](http://developer.telerik.com/featured/300-ms-click-delay-ios-8/) for more info on the topic.
Note that this change does not affect the `ngSwipe` directive.
  • Loading branch information
Narretz committed Jan 27, 2016
1 parent e9c406b commit 0dfc1df
Show file tree
Hide file tree
Showing 3 changed files with 662 additions and 465 deletions.
21 changes: 11 additions & 10 deletions src/ngTouch/directive/ngClick.js
Expand Up @@ -7,8 +7,17 @@
/**
* @ngdoc directive
* @name ngClick
* @deprecated
*
* @description
* <div class="alert alert-danger">
* **DEPRECATION NOTICE**: Beginning with Angular 1.5, this directive is deprecated and by default **disabled**.
* The directive will receive no further support and might be removed from future releases.
* If you need the directive, you can enable it with the {@link ngTouch.$touchProvider $touchProvider#ngClickOverrideEnabled}
* function. We also recommend that you migrate to [FastClick](https://github.com/ftlabs/fastclick).
* To learn more about the 300ms delay, this [Telerik article](http://developer.telerik.com/featured/300-ms-click-delay-ios-8/)
* gives a good overview.
* </div>
* A more powerful replacement for the default ngClick designed to be used on touchscreen
* devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
* the click event. This version handles them immediately, and then prevents the
Expand Down Expand Up @@ -40,15 +49,7 @@
</example>
*/

ngTouch.config(['$provide', function($provide) {
$provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
// drop the default ngClick directive
$delegate.shift();
return $delegate;
}]);
}]);

ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
var ngTouchClickDirectiveFactory = ['$parse', '$timeout', '$rootElement',
function($parse, $timeout, $rootElement) {
var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
Expand Down Expand Up @@ -292,5 +293,5 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
});

};
}]);
}];

101 changes: 101 additions & 0 deletions src/ngTouch/touch.js
@@ -1,5 +1,8 @@
'use strict';

/* global ngTouchClickDirectiveFactory: false,
*/

/**
* @ngdoc module
* @name ngTouch
Expand All @@ -22,6 +25,104 @@
/* global -ngTouch */
var ngTouch = angular.module('ngTouch', []);

ngTouch.provider('$touch', $TouchProvider);

function nodeName_(element) {
return angular.lowercase(element.nodeName || (element[0] && element[0].nodeName));
}

/**
* @ngdoc provider
* @name $touchProvider
*
* @description
* The `$touchProvider` allows enabling / disabling {@link ngTouch.ngClick ngTouch's ngClick directive}.
*/
$TouchProvider.$inject = ['$provide', '$compileProvider'];
function $TouchProvider($provide, $compileProvider) {

/**
* @ngdoc method
* @name $touchProvider#ngClickOverrideEnabled
*
* @param {boolean=} enabled update the ngClickOverrideEnabled state if provided, otherwise just return the
* current ngClickOverrideEnabled state
* @returns {*} current value if used as getter or itself (chaining) if used as setter
*
* @kind function
*
* @description
* Call this method to enable/disable {@link ngTouch.ngClick ngTouch's ngClick directive}. If enabled,
* the default ngClick directive will be replaced by a version that eliminates the 300ms delay for
* click events on browser for touch-devices.
*
* The default is `false`.
*
*/
var ngClickOverrideEnabled = false;
var ngClickDirectiveAdded = false;
this.ngClickOverrideEnabled = function(enabled) {
if (angular.isDefined(enabled)) {

if (enabled && !ngClickDirectiveAdded) {
ngClickDirectiveAdded = true;

// Use this to identify the correct directive in the delegate
ngTouchClickDirectiveFactory.$$moduleName = 'ngTouch';
$compileProvider.directive('ngClick', ngTouchClickDirectiveFactory);

$provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
if (ngClickOverrideEnabled) {
// drop the default ngClick directive
$delegate.shift();
} else {
// drop the ngTouch ngClick directive if the override has been re-disabled (because
// we cannot de-register added directives)
var i = $delegate.length - 1;
while (i >= 0) {
if ($delegate[i].$$moduleName === 'ngTouch') {
$delegate.splice(i, 1);
break;
}
i--;
}
}

return $delegate;
}]);
}

ngClickOverrideEnabled = enabled;
return this;
}

return ngClickOverrideEnabled;
};

/**
* @ngdoc service
* @name $touch
* @kind object
*
* @description
* Provides the {@link ngTouch.$touch#ngClickOverrideEnabled `ngClickOverrideEnabled`} method.
*
*/
this.$get = function() {
return {
/**
* @ngdoc method
* @name $touch#ngClickOverrideEnabled
*
* @returns {*} current value of `ngClickOverrideEnabled` set in the {@link ngTouch.$touchProvider $touchProvider},
* i.e. if {@link ngTouch.ngClick ngTouch's ngClick} directive is enabled.
*
* @kind function
*/
ngClickOverrideEnabled: function() {
return ngClickOverrideEnabled;
}
};
};

}

0 comments on commit 0dfc1df

Please sign in to comment.