Prevenir / lidar com cliques de botão duplo em angular

Em angular, podemos configurar um botão para enviar solicitações ajax como esta:

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

e no controlador:

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

Portanto, para evitar um envio duplo, eu poderia definir um sinalizador para buttonclicked = true quando um botão é clicado e desfeito quando o retorno de chamada do ajax é concluído. Mas, mesmo assim, o controle é tratado de volta para o angular, que será atualizado para o Dom. Isso significa que há uma pequena janela onde o botão pode ser clicado novamente antes que o clique original do botão tenha sido 100% concluído.

É uma pequena janela, mas ainda pode acontecer. Quaisquer dicas para evitar completamente que isso aconteça – do lado do cliente ou seja, sem fazer atualizações para o servidor.

obrigado

Usando ng-disabled funcionou muito bem neste exemplo . Não importa o quão furiosamente eu cliquei na mensagem do console, apenas preenchida uma 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         

Primeiro você deve adicionar ngDblclick , quando ele detecta o duplo clique apenas retorna false :

  

Se você quiser esperar que a chamada do Ajax seja concluída, então você pode desabilitar o botão definindo o ng-disabled

  

E no seu controlador, você pode fazer

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

Você precisa lidar com ambos os casos quando a chamada ajax é bem-sucedida ou falha, pois se ela falhar, você não quer que ela seja mostrada como com diable para confundir o usuário.

Eu apenas expandi o código do zsong para adicionar uma verificação no manipulador da bandeira. Se for verdade, retorne porque um clique já está sendo manipulado. Isso evita cliques duplos sem se preocupar com tempo angular ou esse tipo de coisa.

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

Você pode usar uma diretiva que acabei de concluir para impedir que o usuário clique várias vezes em um elemento ao executar uma ação assíncrona

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


Você pode adicioná-lo ao seu projeto com npm ou bower

 npm install angular-click-and-wait 

ou

 bower install angular-click-and-wait 

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

Eu gostei da solução do usuário: zsong

Mas ng-dblclick = “return false”; dar um problema (estou usando o Chrome Windows7) no console js você pode ver o erro.

Não posso comentar (não tenho reputação suficiente para comentar sua solução)

Apenas use apenas ng-disabled.

Como você pode ver na plunker abaixo, se você tem as duas funções: ng-click e ng-dblclick E dê um clique duplo para executar: 2 vezes clique e 1 vez dblclick

  

O clique duplo dá a você 1.2, então você não pode evitar os 2 cliques com o ng-dblclick, apenas adicione mais um comportamento quando o segundo clique acontecer.

Dblclick e clique

Jonathan Palumbo deu um exemplo com o trabalho ng-disabled neste tópico.

Recentemente tive que fazer isso e trouxe algumas soluções juntas. Isso funciona para mim, é uma diretiva que é uma alternativa ao ng-click que só pode ser clicada uma vez.

Esta solução gera erros, o que tornou super fácil de testar.

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

Aqui estão meus testes (no caso de alguém estar interessado)

 '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 sugerido, usar o ng-disabled resolverá seu problema. Eu fiz uma plunker para ilustrar isso aqui .

para elaborar a resposta do @Jonathan Palumbo (use ngDisabled) e a pergunta do @ andre (“como usar isso em uma diretiva em vez de controlador?”): para permitir que o evento de clique ou de submissão seja preenchido, é necessário definir o ‘desativado’ atribuir ao seu elemento clicável (seja um botão, um link, um intervalo ou um div) programaticamente dentro de uma function de tempo limite (mesmo com um atraso de 0 ms) que permite que o evento seja passado antes de ser desativado:

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

Refiro-me à resposta do @ arun-p-johny: impedir vários envios de formulário usando angular.js – desativar o botão de formulário .

Como sugerido em uma das respostas, tentei usar ng-dbliclick="return false;" que dá aviso JS.


Insted eu usei ng-dblclick="return" que está funcionando bem. Embora isso só funcione dentro da tag

.

Para expandir novamente na corça de zsong:

Primeiro, com essa solução, as pessoas podem usar o clique duplo para usar seu aplicativo. Pessoas idosas em algum momento fazem isso (como eram usadas para clicar duas vezes para abrir um programa no Windows), e outras pessoas fazem isso por engano também.

Segundo, o usuário pode clicar o mais rápido possível, o navegador aguardará a resposta do servidor antes de reativar o botão (é uma correção para o comentário de Mark Rajcok na postagem de zsong: “Se o pedido AJAX demorar mais do que o dobro do navegador -Clique hora / janela, isso não vai funcionar. Ou seja, um usuário pode pausar e clicar novamente. “).

no seu html

  

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

Você pode criar uma diretiva para evitar duplo clique:

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

e você pode usá-lo em qualquer item clicável como este:

  

Uma solução simples que encontrei e acho melhor do que outra resposta aqui está impedindo o comportamento padrão do navegador no evento mousedown.

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

NÃO impede o evento de cliques, mas evita o evento de clique duplo 🙂