Je veux empêcher l'envoi de formulaires multiples à l'aide de angular.js. La question est liée à cette question .
Lorsque l'utilisateur clique sur un bouton de soumission de formulaire, la valeur/l'étiquette du bouton de soumission doit changer en "chargement ..", le statut du bouton est défini sur désactivé et l'événement de soumission doit être déclenché normalement, ce qui entraîne une soumettre l'appel au serveur. De cette façon, l'utilisateur verrait l'effet suivant:
Immédiatement: la valeur du bouton d'envoi devient "chargement .." et est désactivée
Dès que le serveur répond, le résultat de la requête du serveur est présenté à l'utilisateur (alors que la réponse du serveur est traitée sans intervention angulaire)
J'ai créé ce plunk pour montrer ce que je veux dire. Mon problème concerne cette ligne: Elm.attr('disabled',true);
. Cela ne désactive pas seulement le bouton, mais empêche également de propager l'événement submit. Ainsi, je reçois un bouton désactivé (résultat souhaité), mais le formulaire n'est pas soumis (résultat non souhaité).
Vous pouvez voir le changement de comportement si vous commentez/décommentez cette ligne: Elm.attr('disabled',true);
Une idée de comment changer cela?
Essayez un $ timeout (la fonction angularjs)
$timeout(function(){
Elm.attr('disabled',true);
}, 0)
J'ai un formulaire standard et je n'utilise que angular dans le front-end, donc si vous avez juste besoin d'empêcher que l'on clique deux fois sur un bouton pendant que le serveur répond, vous pouvez utiliser cette simple directive qui est réutilisable et ne nécessite aucun contrôleur ou 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);
});
}
};
});
Cela désactivera le bouton et changera éventuellement le texte du bouton. Utilisez comme si:
<button click-once>Button just disables</button>
<button click-once="Loading...">Text changes and button disables</button>
Dans sa forme actuelle, cela ne fonctionnera que si vous soumettez des formulaires standard et non des soumissions ajax.
Il suffit d'ajouter une nouvelle propriété dans votre contrôleur
$scope.processing = false;
Dans votre méthode
$scope.processData = function(){
$scope.processing = true;
$http.post('').then(function(){
$scope.processing = false;
});
});
Dans votre attribut html bind ng-disabled à la propriété $ scope.processing pour désactiver le bouton et afficher le texte pendant le traitement de la méthode.
Solution alternative (flexible et simple) ( inspiration ): une fonction d'encapsulation autour du code de soumission qui définit une variable de portée. Voir exemple live .
Utilisation dans le contrôleur:
$scope.submit = mutexAction($scope, 'sending', submit);
en vue:
<form ng-submit="submit()">
...
<button ng-disabled="sending">
{{sending ? "Sending..." : "Send"}}
</button>
</form>
La fonction (le mettre dans un service):
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;
});
};
}
Un ajout à passéehil réponse, une variante de Coffee Script + vous pouvez réactiver un bouton si vous en avez besoin (par exemple, si une validation de formulaire a échoué et que vous souhaitez réessayer)
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]
J'ai fini par suivre la voie directive. Ce qui suit est utilisé à la place de ng-click et s'attend à ce que la fonction passée dans renvoie une promesse (ce que restangular fait) Lorsque la promesse est résolue (la réponse est renvoyée), les soumissions ultérieures seront autorisées. Pourrait également modifier ceci pour ajouter/supprimer 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 });
});
});
};
}
};
});
voici un moyen simple de faire des choses similaires avec des vérifications logiques simples, le changement de texte peut également être effectué de la même manière.
<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');
}
La façon la plus simple et la plus courante de le faire est de s'appuyer sur la propriété $submitted
de l'objet formulaire
<form name="formName" ng-submit="submit()">
...
<button type="submit" ng-disabled="formName.$submitted">Submit</button>
</form>
Voici un moyen général de le faire pour toutes les requêtes AJAX utilisant des intercepteurs $ http. Si vous avez tous vos REST routes à partir de/api /, alors:
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');
}]);
En utilisant interceptor, vous n'aurez pas besoin de mettre $ scope.isLoading dans chaque contrôleur. L'inconvénient est que tout bouton avec ng-disabled = "loading" sera bloqué pendant la requête.