防止使用 angular.js 提交多个表单 - 禁用表单按钮
Posted
技术标签:
【中文标题】防止使用 angular.js 提交多个表单 - 禁用表单按钮【英文标题】:prevent multiple form submissions using angular.js - disable form button 【发布时间】:2013-04-04 09:31:19 【问题描述】:我想防止使用 angular.js 提交多个表单。问题与这个question有关。
当用户单击表单提交按钮时,提交按钮的值/标签应更改为“正在加载..”,按钮的状态将设置为禁用,并且应以正常方式触发提交事件,导致对服务器的提交调用。这样用户会看到以下效果:
立即:提交按钮值更改为“正在加载..”并被禁用
只要服务器响应:用户就会得到服务器请求的结果(而服务器响应是在没有角度干预的情况下处理的)
我创建了这个plunk 来表达我的意思。我的问题与这一行有关:elm.attr('disabled',true);
。这不仅会禁用按钮,还会阻止传播提交事件。因此,我得到了一个禁用的按钮(期望的结果),但没有提交表单(不期望的结果)。
如果您评论/取消评论这一行,您可以看到变化的行为:elm.attr('disabled',true);
知道怎么改吗?
【问题讨论】:
Preventing / dealing with double button clicks in angular的可能重复 【参考方案1】:我有一个标准的表单,只在前端使用 angular,所以如果你只需要防止在服务器响应时点击两次按钮,那么你可以使用这个简单的指令,它可以重复使用并且不需要控制器或 ngModel。
http://plnkr.co/edit/2aZWQSLS8s6EhO5rKnRh?p=preview
app.directive('clickOnce', function($timeout)
return
restrict: 'A',
link: function(scope, element, attrs)
var replacementText = attrs.clickOnce;
element.bind('click', function()
$timeout(function()
if (replacementText)
element.html(replacementText);
element.attr('disabled', true);
, 0);
);
;
);
它将禁用按钮并可选择更改按钮的文本。像这样使用:
<button click-once>Button just disables</button>
<button click-once="Loading...">Text changes and button disables</button>
在当前的表单中,这仅在您进行标准表单提交而不是 ajax 提交时才有效。
【讨论】:
几乎完美,但是它不适用于 ajax。对 ajax 提交有什么建议吗? 是的,我的案例是故意简化的,它是为了与标准表单提交集成。 AJAX 请求有许多解决方案,从重新启用响应按钮到通用挂钩。例如这里有一篇博文:blog.codebrag.com/post/57412530001/…。就像我说的,我特意选择了一个简化版本,用于标准表单提交、易于测试等。 @MattByrne 这很棒,我已经将它实现到我的表单中。但它不允许人们更正无效字段并重新提交。提交按钮将永久禁用。关于如何解决这个问题的任何想法?谢谢! 我已经有一段时间没有看这个了。这些天我通常使用 ajax 请求——这是我为之做的遗留应用程序。如果你想有条件地禁用按钮,你可以传递一个function
,它返回一个boolean
作为指令的参数。您可以在$timeout
之前执行if (!conditionFunction || conditionFunction())
。您需要定义一个名为 conditionFunction
的 scope
参数,该参数是可选的。当您调用它时,您将传递参数condition-function="myControllerFunction"
。【参考方案2】:
只需在控制器中添加一个新属性
$scope.processing = false;
在你的方法中
$scope.processData = function()
$scope.processing = true;
$http.post('').then(function()
$scope.processing = false;
);
);
在您的 html 中将 ng-disabled 属性绑定到 $scope.processing 属性,以在方法处理时禁用按钮并显示文本。
【讨论】:
在我看来,基本上是最简单、最灵活的方法【参考方案3】:试试 $timeout(angularjs 函数)
$timeout(function()
elm.attr('disabled',true);
, 0)
【讨论】:
谢谢,这似乎可以解决问题 :) plnkr.co/edit/pl7d7uCuA4tRbproT5PP?p=preview .. 你能解释一下为什么吗? ..为什么超时为零? 超时允许浏览器先提交,然后禁用按钮。setTimeout
调用中的函数放置在浏览器的事件循环中,并在表单提交后立即执行。没有它,按钮就太早被禁用了。
可能应该使用$timeout
代替?即将 $timeout 参数添加到您的指令函数中,然后使用 $timeout(function() elm.attr('disabled',true); , 0);
【参考方案4】:
替代(灵活和简单)解决方案(inspiration):围绕提交代码设置范围变量的包装函数。见live example。
在控制器中的使用:
$scope.submit = mutexAction($scope, 'sending', submit);
在视图中:
<form ng-submit="submit()">
...
<button ng-disabled="sending">
sending ? "Sending..." : "Send"
</button>
</form>
函数(放在服务中):
function mutexAction(scope, semaphoreName, action)
return function()
if (scope[semaphoreName])
// not queuing action, forget it!
return;
scope[semaphoreName] = true;
action()['finally'](function ()
scope[semaphoreName] = false;
);
;
【讨论】:
【参考方案5】:spenthil 答案的补充,咖啡脚本中的一个变体 + 如果需要,您可以启用一个返回按钮(例如,当表单验证失败并且您想重试时)
class ClickOnceDirective
constructor: (@$timeout) ->
link = (scope, element, attrs) =>
originalText = element.html()
replacementText = attrs.clickOnce
element.bind('click', =>
@$timeout ->
if (replacementText)
element.html(replacementText)
element.attr('disabled', true)
# enable back
@$timeout ->
element.attr('disabled', false)
if (replacementText)
element.html(originalText)
, 500
, 0)
return
link
restrict: 'A'
directivesModule.directive 'clickOnce', ['$timeout', ClickOnceDirective]
【讨论】:
【参考方案6】:最简单和最常见的方法是依赖表单对象的$submitted
属性,如下所示:
<form name="formName" ng-submit="submit()">
...
<button type="submit" ng-disabled="formName.$submitted">Submit</button>
</form>
【讨论】:
【参考方案7】:我最终也选择了指令路线。以下用于代替 ng-click 并期望传入的函数返回一个承诺(restangular 会这样做)。当承诺被解决(响应被返回) - 它将允许后续提交。也可以调整它以添加/删除 ng-disabled。
// copied from ngClick and modified to provide single click only behavior
// expects function to return promise
app.directive('srMutexClick', function ($parse)
return
compile: function ($element, attr)
var fn = $parse(attr['srMutexClick']);
return function srEventHandler(scope, element)
var submitting = false;
element.on('click', function (event)
scope.$apply(function ()
if (submitting)
return
submitting = true;
// `submitting` is reset when promise is resolved
fn(scope, $event: event).finally(function() submitting = false );
);
);
;
;
);
【讨论】:
【参考方案8】:这是用简单的逻辑检查来做类似事情的简单方法,文本更改也可以用类似的方式完成。
<button type="submit" class="btn btn-success btn-lg btn-block" ng-disabled="!userForm.$valid || isValid">Submit!</button>
$scope.isValid = false;
$scope.submitForm = function ()
$scope.isValid = true;
console.log('submit');
【讨论】:
【参考方案9】:这是使用 $http 拦截器对所有 AJAX 请求执行此操作的通用方法。 如果您的所有 REST 路由都从 /api/ 开始,那么:
angular.module('yourapp').factory('loadingInterceptor',['$q','$rootScope',function($q,$rootScope)
var apiRe = /^\/api\//;
return
request: function(config)
if (config.url.match(apiRe))
$rootScope.loading = true;
config.headers = config.headers || ;
return config;
,
response: function(res)
$rootScope.loading = false;
return $q.resolve(res);
,
'responseError': function(rejection)
$rootScope.loading = false;
return $q.reject(rejection);
;
]);
angular.module('yourapp').config(['$httpProvider', function($httpProvider)
$httpProvider.interceptors.push('loadingInterceptor');
]);
使用拦截器,您不必将 $scope.isLoading 放在每个控制器中。缺点是任何带有 ng-disabled="loading" 的按钮都会在请求期间被阻止。
【讨论】:
以上是关于防止使用 angular.js 提交多个表单 - 禁用表单按钮的主要内容,如果未能解决你的问题,请参考以下文章
当表单出现错误时,带有 angular.js 表单提交的 Django 不起作用