Evitar / tratar con clics de doble botón en angular

En angular podemos configurar un botón para enviar solicitudes ajax como esta a la vista:

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

y en el controlador:

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

Entonces, para evitar un doble envío, podría establecer un indicador para buttonclicked = true cuando se haga clic en un botón y se desarmará cuando termine la callback ajax. Pero, incluso entonces, el control se maneja de nuevo a angular quién actualizará el Dom. Eso significa que hay una pequeña ventana donde se podría hacer clic de nuevo en el botón antes de que el clic original del botón haya finalizado por completo al 100%.

Es una ventana pequeña, pero aún puede suceder. Cualquier sugerencia para evitar por completo que esto suceda, es decir, del lado del cliente, sin hacer ninguna actualización al servidor.

Gracias

Usar ng-disabled funcionó bien en este ejemplo . No importa cuán furiosamente hice clic en el mensaje de la consola solo se llenó una vez.

 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(); }); 
     AngularJS Plunker         

Primero, será mejor que agregue ngDblclick , cuando detecte el doble clic simplemente devuelva false :

  

Si desea esperar a que finalice la llamada de Ajax, puede desactivar el botón configurando ng-disabled

  

Y en tu controlador, puedes hacer

 $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; }) } 

Debe manejar ambos casos cuando la llamada ajax sea exitosa o fallida, ya que si falla, no desea que se muestre como diableado para confundir al usuario.

Acabo de expandir el código de zsong para agregar una marca en el controlador de la bandera. Si es verdadero, solo regrese porque ya se está manejando un clic. Esto evita doble clic sin preocuparse por el tiempo angular o ese tipo de cosas.

 $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; }) } 

Puede usar una directiva que acabo de terminar para evitar que el usuario haga clic varias veces en un elemento al realizar una acción asincrónica.

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


Puede agregarlo a su proyecto con npm o bower

 npm install angular-click-and-wait 

o

 bower install angular-click-and-wait 

Ejemplo de uso

 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); } }); 
   

Me gustó la solución del usuario: zsong

Pero ng-dblclick = “return false;” da un problema (estoy usando Chrome Windows 7) en la consola js puede ver el error.

No puedo comentar (no tengo suficiente reputación para comentar su solución)

Solo use ng-disabled.

Como puede ver en el plunker a continuación si tiene las dos funciones: ng-clic y ng-dblclick Y dé un doble clic que ejecutará: 2 veces clic y 1 vez dblclick

  

El doble clic le da 1.2, por lo que no puede evitar los 2 clics con ng-dblclick, solo agregue un comportamiento más cuando ocurra el segundo clic.

Haga clic y haga clic

Jonathan Palumbo dio un ejemplo con ng-disabled trabajo en este hilo.

Recientemente tuve que hacer esto, y he traído un par de soluciones juntas. Esto funciona para mí, es una directiva que es una alternativa a ng-click que solo se puede hacer clic una vez.

Esta solución arroja errores, lo que hace que sea muy fácil de probar.

 .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 }; } ]) 

Aquí están mis pruebas (en caso de que alguien esté interesado)

 '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 = "test button"; 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 = "test button"; 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 = "test button"; var html2 = "test button"; 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); }); }); 

Como se sugiere, usar ng-disabled resolverá su problema. Hice un plunker para ilustrarlo aquí .

para explicar la respuesta de @Jonathan Palumbo (use ngDisabled) y la pregunta de @andre (“¿cómo usar eso en una directiva en lugar de un controlador?”): para permitir que el evento de clic o presentación suba, debe configurar el ‘desactivado’ atribuir a su elemento cliqueable (ya sea un botón, un enlace, un tramo o un div) programáticamente dentro de una función de tiempo de espera (incluso con un retraso de 0 ms) que permite que el evento se pase antes de que se deshabilite:

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

Me refiero a la respuesta de @arun-p-johny: evitar múltiples envíos de formularios usando angular.js – desactivar el botón de formulario .

Como se sugirió en una de las respuestas, traté de usar ng-dbliclick="return false;" lo que le da a JS una advertencia.


Insistí que usé ng-dblclick="return" que funciona sin problemas. Aunque esto solo funciona dentro de la etiqueta

.

Para expandir nuevamente en la gama de zsong:

En primer lugar, con esta solución, las personas pueden usar el doble clic para usar su aplicación. Las personas mayores alguna vez hacen eso (ya que fueron usadas para hacer doble clic para abrir un progtwig en Windows), y otras personas también lo hacen por error.

Segundo, el usuario puede hacer clic tan rápido como pueda, su navegador esperará la respuesta del servidor antes de volver a habilitar el botón (es una solución para el comentario de Mark Rajcok en la publicación de zsong: “Si la solicitud AJAX tarda más que el doble del navegador -click time / window, esto no funcionará. Es decir, un usuario podría hacer una pausa y volver a hacer clic. “).

en tu html

  

en tu controlador

 $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 --; }) } 

Puede crear una directiva para evitar el doble clic:

 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); }); } }; }); 

y puedes usarlo en cualquier elemento cliqueable como este:

  

Una solución simple que encontré y creo que es mejor que otra respuesta aquí es evitar el comportamiento predeterminado del navegador en el evento mousedown.

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

NO previene el evento de clic, pero impide el doble clic en el evento 🙂