Angle.jsを使用した複数のフォーム送信を防ぎたい。質問はこれに関連しています 質問 。
ユーザーがフォーム送信ボタンをクリックすると、送信ボタンの値/ラベルが「loading ..」に変更され、ボタンのステータスが無効に設定され、送信イベントが通常の方法でトリガーされ、サーバーに呼び出しを送信します。このようにして、ユーザーには次の効果が表示されます。
すぐに:送信ボタンの値が「loading ..」に変わり、無効になります
サーバーが応答するとすぐに:ユーザーはサーバー要求の結果を提示されます(サーバーの応答はangular介入なしで処理されます)
私はこれを作成しました plunk 私が何を意味するかを示すために。私の問題はこの行に関連しています:Elm.attr('disabled',true);
。これにより、ボタンが無効になるだけでなく、送信イベントの伝播も防止されます。したがって、無効化されたボタン(望ましい結果)が表示されますが、フォームは送信されません(望ましくない結果)。
この行にコメント/コメントを外すと、動作の変化を確認できます:Elm.attr('disabled',true);
これを変更する方法はありますか?
$ timeout(angularjs関数)を試してください
$timeout(function(){
Elm.attr('disabled',true);
}, 0)
私は標準のフォームを持っており、フロントエンドでangularを使用するだけなので、サーバーの応答中にボタンが2回クリックされるのを防ぐ必要がある場合は、この単純なディレクティブを使用できます。再利用可能で、コントローラーや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送信は行いません。
コントローラに新しいプロパティを追加するだけです
$scope.processing = false;
あなたの方法で
$scope.processData = function(){
$scope.processing = true;
$http.post('').then(function(){
$scope.processing = false;
});
});
HTMLでng-disabled属性を$ scope.processingプロパティにバインドして、メソッドの処理中にボタンを無効にし、テキストを表示します。
代替(柔軟でシンプルな)ソリューション( インスピレーション ):スコープ変数を設定する送信コードのラッパー関数。 実例 を参照してください。
コントローラでの使用法:
$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;
});
};
}
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]
私も指令ルートに行くことになりました。 ng-clickの代わりに以下が使用され、渡された関数がpromiseを返すことを期待します(これは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 });
});
});
};
}
};
});
これは、単純な論理チェックで同様のことを行う簡単な方法です。テキストの変更も同様の方法で行うことができます。
<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');
}
これを行う最も簡単で一般的な方法は、次のようにフォームオブジェクトの$submitted
プロパティに依存することです。
<form name="formName" ng-submit="submit()">
...
<button type="submit" ng-disabled="formName.$submitted">Submit</button>
</form>
これは、$ httpインターセプターを使用してすべてのAJAXリクエストに対して行う一般的な方法です。/api/から始まるすべてのRESTルートがある場合は、次のようになります。
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"のボタンがリクエスト中にブロックされることです。