Предотвращение/обращение с двойными нажатиями кнопок в angular

В angular мы можем настроить кнопку для отправки таких запросов ajax, как это:

... ng-click="button-click"

и в контроллере:

...
$scope.buttonClicked = function() {
   ...
   ...
   // make ajax request 
   ...
   ...
}

Итак, чтобы предотвратить двойную отправку, я мог установить флаг buttonclicked = true, когда кнопка щелкнула и отменила его, когда завершится обратный вызов ajax. Но даже после этого управление возвращается к angular, который будет обновляться до Dom. Это означает, что есть маленькое окно, где кнопка может быть нажата снова, прежде чем исходный щелчок на кнопке будет полностью завершен на 100%.

Это небольшое окно, но все же может случиться. Любые советы, чтобы полностью избежать этого - клиентская сторона, то есть без каких-либо обновлений для сервера.

Спасибо

Ответ 1

Использование ng-disabled работало просто отлично в этом примере. Независимо от того, как яростно щелкнул консоль, сообщение попало только один раз.

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.submitData = function() {
    $scope.buttonDisabled = true;
    console.log("button clicked");
  }

  function augment() {
    var name, fn;
    for (name in $scope) {
      fn = $scope[name];
      if (typeof fn === 'function') {
        if (name.indexOf("$") !== -1) {
          $scope[name] = (function(name, fn) {
            var args = arguments;
            return function() {
              console.log("calling " + name);
              console.time(name);
              fn.apply(this, arguments);
              console.timeEnd(name);
            }
          })(name, fn);
        }
      }
    }
  }

  augment();
});
<!doctype html>
<html ng-app="plunker">

<head>
  <meta charset="utf-8">
  <title>AngularJS Plunker</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js"></script>
  <script src="app.js"></script>
</head>

<body ng-controller="MainCtrl">
  <input type="button" ng-click="submitData()" ng-disabled="buttonDisabled" value="Submit" />
</body>

</html>

Ответ 2

Сначала вам лучше добавить ngDblclick, когда он обнаружит двойной щелчок, просто верните false:

<ANY ng-click="buttonClicked()" ng-dblclick="return false">

Если вы хотите дождаться завершения вызова Ajax, вы можете отключить эту кнопку, установив ng-disabled

<ANY ng-click="buttonClicked()" ng-dblclick="return false;" ng-disabled="flag">

И в вашем контроллере вы можете сделать

$scope.flag = false;
$scope.buttonClicked = function() {
    $scope.flag = true;
    Service.doService.then(function(){
        //this is the callback for success
        $scope.flag = false;
    }).error(function(){
        //this is the callback for the error
        $scope.flag = false;
    })
}

Вам нужно обрабатывать оба случая, когда вызов ajax успешный или неудачный, поскольку, если он не работает, вы не хотите, чтобы он отображался как disabled, чтобы запутать пользователя.

Ответ 3

Я просто расширил код zsong, чтобы добавить проверку в обработчик для флага. Если его true, то просто возвращайтесь, потому что клик уже обрабатывается. Это предотвращает двойные щелчки, не беспокоясь о сроках angular или о том, что происходит.

$scope.flag = false;
$scope.buttonClicked = function() {
    if ($scope.flag) {
        return;
    }
    $scope.flag = true;
    Service.doService.then(function(){
        //this is the callback for success
        $scope.flag = false;
    }).error(function(){
        //this is the callback for the error
        $scope.flag = false;
    })
}

Ответ 4

Вы можете использовать только что законченную директиву, чтобы пользователь не нажимал несколько раз на элемент при выполнении асинхронного действия

https://github.com/mattiascaricato/angular-click-and-wait


Вы можете добавить его в свой проект с помощью npm или bower

npm install angular-click-and-wait

или же

bower install angular-click-and-wait

Пример использования

const myApp = angular.module('myApp', ['clickAndWait']);

myApp.controller('myCtrl', ($scope, $timeout) => {
  $scope.asyncAction = () => {
    // The following code simulates an async action
    return $timeout(() => angular.noop, 3000);
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="https://cdn.rawgit.com/mattiascaricato/angular-click-and-wait/master/dist/click-and-wait.js"></script>

<section ng-app="myApp">
  <div ng-controller="myCtrl">
    <button click-and-wait="asyncAction()">Click Me!</button>
  </div>
</section>

Ответ 5

Мне понравилось решение пользователя: zsong

Но ng-dblclick = "return false;" (я использую Chrome Windows7), на консоли js вы можете увидеть эту ошибку.

Я не могу комментировать (у меня недостаточно репутации, чтобы прокомментировать его решение)

Просто используйте только ng-disabled.

Как вы можете видеть на плунжере ниже, если у вас есть две функции: ng-click и ng-dblclick И сделайте двойной щелчок, который вы выполните: 2 раза клик и 1 раз dblclick

<bla ng-dblclick="count=count+1" ng-click="count=count+0.1" />

Двойной щелчок дает вам 1.2, поэтому вы не можете предотвратить 2 щелчка с помощью ng-dblclick, просто добавьте еще одно поведение, когда произойдет второй клик.

Dblclick и нажмите

Джонатан Палумбо привел пример с ng-отключенной работой в этом потоке.

Ответ 6

Недавно мне пришлось это сделать, и я привел пару решений вместе. Это работает для меня, это директива, которая является альтернативой ng-click, который можно только щелкнуть один раз.

Это решение выдает ошибки, которые сделали его очень легким для тестирования.

.directive('oneClickOnly', [
    '$parse', '$compile', function($parse, $compile) {
        return {
            restrict: 'A',
            compile: function(tElement, tAttrs) {

                if (tAttrs.ngClick)
                    throw "Cannot have both ng-click and one-click-only on an element";

                tElement.attr('ng-click', 'oneClick($event)');
                tElement.attr('ng-dblclick', 'dblClickStopper($event)');

                tElement.removeAttr('one-click-only');
                var fn = $parse(tAttrs['oneClickOnly']);

                return {
                    pre: function(scope, iElement, iAttrs, controller) {
                        console.log(scope, controller);
                        var run = false;
                        scope.oneClick = function(event) {
                            if (run) {
                                throw "Already clicked";
                            }
                            run = true;
                            $(event.toElement).attr('disabled', 'disabled');

                            fn(scope, { $event: event });

                            return true;
                        };
                        scope.dblClickStopper = function(event) {
                            event.preventDefault();
                             throw "Double click not allowed!";
                            return false;
                        };

                        $compile(iElement)(scope);
                    }
                };
            },
            scope: true
        };
    }
])

Вот мои тесты (в случае, если кто-то заинтересован)

'use strict';
describe("The One click button directive", function() {
var $scope, testButton, $compile, clickedEvent;
var counter = 0;

beforeEach(function () {
    counter = 0;

    module('shared.form.validation');

    inject(function ($rootScope, _$compile_) {

        $compile = _$compile_;
        $scope = $rootScope.$new();

        $scope.clickEvent = function (event) {
            counter++;
        };
    });
});

it("prevents a button from being clicked multiple times", function () {

    var html = "<a one-click-only='clickEvent()'>test button</a>";
    testButton = $compile(html)($scope);

    $scope.$digest();

    testButton.click();
    expect(function () { testButton.click(); }).toThrow("Already clicked");

    expect(counter).toBe(1);
});

it("doesn't allow ng-click on the same tag", function() {
    var html = "<a ng-click='clickEvent()' one-click-only='clickEvent()'>test button</a>";
    expect(function () { $compile(html)($scope); }).toThrow("Cannot have both ng-click and one-click-only on an element");
});

it("works for multiple buttons on the same scope", function () {

    var counter2 = 0;
    $scope.clickEvent2 = function (event) {
        counter2++;
    };

    var html = "<a one-click-only='clickEvent()'>test button</a>";
    var html2 = "<a one-click-only='clickEvent2()'>test button</a>";
    testButton = $compile(html)($scope);
    var testButton2 = $compile(html2)($scope);

    $scope.$digest();

    testButton.click();
    expect(function () { testButton2.click(); }).not.toThrow("Already clicked");

    expect(counter).toBe(1);
    expect(counter2).toBe(1);
});
});

Ответ 7

Как было предложено, использование ng-disabled решит вашу проблему. Я сделал plunker, чтобы проиллюстрировать его здесь.

Ответ 8

чтобы уточнить ответ @Jonathan Palumbo (используйте ngDisabled) и @andre вопрос ( "как использовать это в директиве вместо контроллера?" ): чтобы позволить событию клика или представления пузыриться, вам нужно установить атрибут "отключен" для вашего интерактивного элемента (будь то кнопка, ссылка, span или div) программно внутри функции тайм-аута (даже с задержкой 0 мс), которая позволяет передать событие до его отключения:

$timeout(function(){ elm.attr('disabled',true); }, 0);

Я ссылаюсь на @arun-p-johny answer: предотвращать появление нескольких форм с помощью angular.js - отключить кнопку формы.

Ответ 9

Вы можете создать директиву для предотвращения двойного щелчка:

angular.module('demo').directive('disableDoubleClick', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, elem, attrs) {
            elem.bind('click', function(){
                $timeout(function(){
                    elem.attr('disabled','disabled');
                }, 20);

                $timeout(function(){
                    elem.removeAttr('disabled');
                }, 500);
            });
        }
    };
});

и вы можете использовать его для любого кликабельного элемента, подобного этому:

<button ng-click="clickMe()" disable-double-click >Click Me</button>

Ответ 10

Как было предложено в одном из ответов, я попытался использовать ng-dbliclick="return false;", который дает предупреждение JS.


Я использовал ng-dblclick="return", который работает плавно. Хотя это работает только внутри тега <form>.

Ответ 11

Чтобы снова развернуть zsong doe:

Во-первых, с помощью этого решения люди могут использовать двойной клик для использования вашего приложения. Старые люди когда-то делают это (поскольку они использовались для двойного щелчка, чтобы открыть программу на окнах), а другие тоже делают это по ошибке тоже.

Во-вторых, пользователь может нажимать так быстро, как только может, их браузер будет ждать ответа сервера, прежде чем снова включить кнопку (это исправление для комментария Марка Райкока к сообщению zsong: Если запрос AJAX занимает больше времени, чем браузер, дважды щелкните время/окно, это не сработает. То есть пользователь может сделать паузу и нажать еще раз. ").

в вашем html

<ANY
  ng-click="buttonClicked();
            submitButtonDisabled = 1 + submitButtonDisabled;" 
  ng-disabled="submitButtonDisabled > 0;"
  ng-init="submitButtonDisabled = 0;"
>

в вашем контроллере

$scope.buttonClicked = function() {
    Service.doService.then(function(){
        //this is the callback for success
        // you probably do not want to renable the button here : the user has already sent the form once, that it - but just if you want to :
        $scope.submitButtonDisabled --;
        //display a thank you message to the user instead
        //...
    }).error(function(){
        //this is the callback for the error
        $scope.submitButtonDisabled --;
    })
}

Ответ 12

Простое решение, которое я нашел, и я думаю, лучше, чем другой ответ здесь, это предотвращение поведения браузера по умолчанию в событии mousedown.

ng-mousedown="$event.preventDefault();"

Он не предотвращает событие click, но предотвращает событие двойного щелчка:)

Ответ 13

You can handle the form validation 
    $('form.your-form').validate({
        rules: {
            name: 'required',
            email: {
                required: true,
                email: true
            }
        },
        submitHandler: function (form) {
            // Prevent double submission
            if (!this.beenSubmitted) {
                this.beenSubmitted = true;
                form.submit();
            }
        }
    });