42

在 Angular 中,我们可以设置一个按钮来发送这样的 ajax 请求:

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

在控制器中:

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

因此,为了防止双重提交,我可以在单击按钮时将标志设置为 buttonclicked = true,并在 ajax 回调完成时取消设置。但是,即使这样,控制权也会被处理回 angular,谁将更新 Dom。这意味着有一个小窗口可以在原始按钮单击完全 100% 完成之前再次单击该按钮。

这是一个小窗口,但仍然可能发生。完全避免这种情况发生的任何提示 - 客户端,即无需对服务器进行任何更新。

谢谢

4

13 回答 13

41

首先你最好添加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 调用成功或失败时,您需要处理这两种情况,因为如果它失败,您不希望它显示为已禁用以混淆用户。

于 2013-08-08T16:08:59.893 回答
35

在这个例子中使用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>

我很好奇角度将更改应用于buttonDisabled标志需要多长时间。如果您检查 plunker示例中的控制台,它会显示执行$eval$apply方法需要多长时间。在我的机器上,平均需要 1-2 毫秒。

于 2013-08-08T16:17:00.420 回答
14

我刚刚扩展了 zsong 的代码,在处理程序中添加了一个检查标志。如果它是真的,那么就返回,因为点击已经被处理了。这可以防止双击而不用担心角度时间或类似的事情。

$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;
    })
}
于 2014-05-16T14:06:10.513 回答
7

您可以使用我刚刚完成的指令来防止用户在执行异步操作时多次单击元素

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


您可以使用npmbower将其添加到您的项目中

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>

注意:作为参数传递的异步操作应该是 Promise

于 2016-12-03T19:15:35.683 回答
4

我喜欢用户的解决方案:zsong

但是 ng-dblclick="return false;" 在 js 控制台上给出一个问题(我正在使用 Chrome Windows7)你可以看到错误。

我无法评论(我没有足够的声誉来评论他的解决方案)

只使用 ng-disabled。

如果您有两个功能,您可以在下面的 plunker 中看到:ng-click 和 ng-dblclick 双击您将执行:2 次 click 和 1 次 dblclick

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

双击为您提供 1.2,因此您无法使用 ng-dblclick 阻止 2 次单击,只需在第二次单击发生时再添加一个行为。

Dblclick 并单击

Jonathan Palumbo 在这个线程中给出了一个 ng-disabled 工作的例子。

于 2015-05-07T14:42:53.867 回答
2

我最近不得不这样做,我把几个解决方案放在一起。这对我有用,它是一个替代 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);
});
});
于 2016-04-26T09:00:31.357 回答
1

详细说明@Jonathan Palumbo 的答案(使用 ngDisabled)和@andre 的问题(“如何在指令而不是控制器中使用它?”):要允许单击或提交事件冒泡,您需要设置“禁用”以编程方式在超时函数内(即使延迟为 0 毫秒)赋予您的可点击元素(无论是按钮、链接、跨度还是 div),该函数允许在事件被禁用之前传递事件:

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

我参考@arun-p-johny 的回答:使用 angular.js 阻止多个表单提交 - 禁用表单按钮

于 2014-10-15T08:22:26.267 回答
1

如建议的那样,使用ng-disabled将解决您的问题。我在这里做了一个 plunker 来说明它。

于 2013-08-08T16:19:10.500 回答
1

您可以创建一个指令来防止双击:

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>
于 2017-07-19T14:19:53.597 回答
0

正如其中一个答案所建议的,我尝试使用ng-dbliclick="return false;"which 给出 JS 警告。


我使用的 Instedng-dblclick="return"运行顺利。虽然这只适用于<form>标签内。

于 2015-05-14T17:41:22.260 回答
0
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();
            }
        }
    });
于 2019-05-03T06:17:16.873 回答
0

再次扩展 zsong 的 doe :

首先,有了这个解决方案,人们可​​以通过双击来使用你的应用程序。老年人有时会这样做(因为他们习惯于双击打开 Windows 上的程序),其他人也会错误地这样做。

其次,用户可以尽可能快地点击,他们的浏览器会在重新启用按钮之前等待服务器响应(这是对 Mark Rajcok 对 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's 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 --;
    })
}
于 2015-11-12T10:18:36.057 回答
0

我发现一个简单的解决方案,我认为比这里的其他答案更好的是防止浏览器在 mousedown 事件上的默认行为。

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

它不会阻止单击事件,但会阻止双击事件:)

于 2017-12-04T15:54:17.957 回答