In angular we can set up a button to send ajax requests like this in view:
... ng-click=\"button-click\"
and in controller:
As suggested, using ng-disabled
will solve your problem. I made a plunker to illustrate it here.
Using ng-disabled
worked just fine in this example. No matter how furiously I clicked the console message only populated once.
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>
I was curious exactly how long it takes for angular to apply the changes to the buttonDisabled
flag. If you check the console in the plunker example it displays how long it takes the $eval
and $apply
methods to execute. On my machine it took an average of between 1-2 milliseconds.
I recently had to do this, and I've brought a couple of solutions together. This works for me, it is a directive that is an alternative to ng-click that can only ever be clicked once.
This solution throws errors, which made it super easy to test.
.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
};
}
])
Here are my tests (in case anybody is interested)
'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);
});
});
To expand again on zsong's doe :
First, with this solution, people can use double click to use your app. Old people sometime do that (as they were used to double click to open a program on windows), and other people do that by mistake too.
Second, the user can click as quickly as they can, their browser will wait for the server response before re-enabling the button (it is a fix for Mark Rajcok's comment on zsong's post : "If the AJAX request takes longer than the browser's double-click time/window, this won't work. I.e., a user could pause and click again.").
in your html
<ANY
ng-click="buttonClicked();
submitButtonDisabled = 1 + submitButtonDisabled;"
ng-disabled="submitButtonDisabled > 0;"
ng-init="submitButtonDisabled = 0;"
>
in your controller
$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 --;
})
}
As suggested in one of the answers, I tried using ng-dbliclick="return false;"
which gives JS warning.
Insted I used ng-dblclick="return"
which is working smooth. Though this only works inside the <form>
tag.
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();
}
}
});