Qual é a melhor maneira de cancelar a propagação de eventos entre chamadas ng-click aninhadas?

180

Aqui está um exemplo. Digamos que eu queira ter uma sobreposição de imagem como muitos sites. Portanto, quando você clica em uma miniatura, uma sobreposição preta aparece sobre toda a janela e uma versão maior da imagem é centralizada nela. Clicar na sobreposição preta a dispensa; clicar na imagem chama uma função que mostra a próxima imagem.

O html:

<div ng-controller="OverlayCtrl" class="overlay" ng-click="hideOverlay()">
    <img src="http://some_src" ng-click="nextImage()"/>
</div>

O javascript:

function OverlayCtrl($scope) {
    $scope.hideOverlay = function() {
        // Some code to hdie the overlay
    }
    $scope.nextImage = function() {
        // Some code to find and display the next image
    }
}

O problema é que, com esta configuração, se você clicar na imagem, tanto nextImage()e hideOverlay()são chamados. Mas o que eu quero é que apenas nextImage()seja chamado.

Eu sei que você pode capturar e cancelar o evento na nextImage()função assim:

if (window.event) {
    window.event.stopPropagation();
}

... Mas quero saber se existe uma maneira melhor de fazer o AngularJS que não exija que eu prefixe todas as funções nos elementos dentro da sobreposição com esse trecho.

cilphex
fonte
1
return falseno final da função
Jonathan de M.
1
Eu acredito que é a maneira de fazer isso onclick, não ng-click.
Michael Scheper

Respostas:

193

O que o @JosephSilber disse, ou passe o objeto $ event para o ng-clickretorno de chamada e interrompa a propagação dentro dele:

<div ng-controller="OverlayCtrl" class="overlay" ng-click="hideOverlay()">
  <img src="http://some_src" ng-click="nextImage($event)"/>
</div>
$scope.nextImage = function($event) {
  $event.stopPropagation();
  // Some code to find and display the next image
}
Stewie
fonte
8
use $ event.preventDefault (); em v> 1.5
KoolKabin 17/08/16
3
@KoolKabin Estou usando Angular 1.5.8 e $ event.stopPropagation () ainda funciona bem. $ event.preventDefault (), no entanto, não funciona
HammerNL
1
Estranho, porque eu tenho a situação oposta. stopPropagation não funciona mais, mas preventDefault () funciona. A única diferença é que liguei diretamente no ng-click. <tr ng-click = "ctr.foo (); $ event.preventDefault ()"> ..... </tr>
MilitelloVinx
171

Use $event.stopPropagation():

<div ng-controller="OverlayCtrl" class="overlay" ng-click="hideOverlay()">
    <img src="http://some_src" ng-click="nextImage(); $event.stopPropagation()" />
</div>

Aqui está uma demonstração: http://plnkr.co/edit/3Pp3NFbGxy30srl8OBmQ?p=preview

Joseph Silber
fonte
Eu entro ReferenceError: $event is not definedno console.
cilphex
Sim, funciona ... também funciona quando escrevo uma versão simples separada do zero. Estou tentando descobrir o que poderia fazer com que não funcionasse no meu código de desenvolvimento. Não sei: \
cilphex 4/13
@cilphex - Você está usando uma versão atualizada do Angular?
Joseph Silber
Sim - acabou de encontrar o problema. Ao testar, tentei colocar $event.stopPropagation()em um lugar onde não funcionasse. Esqueceu de removê-lo. Então, mesmo quando eu o coloquei onde funcionava, estava sendo chamado pela segunda vez onde não funcionava. Livre-se da duplicata disfuncional e é bem-sucedida. Obrigado pela ajuda.
cilphex
101

Eu gosto da ideia de usar uma diretiva para isso:

.directive('stopEvent', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            element.bind('click', function (e) {
                e.stopPropagation();
            });
        }
    };
 });

Em seguida, use a diretiva como:

<div ng-controller="OverlayCtrl" class="overlay" ng-click="hideOverlay()">
    <img src="http://some_src" ng-click="nextImage()" stop-event/>
</div>

Se você quiser, poderá tornar esta solução mais genérica como esta, para uma pergunta diferente: https://stackoverflow.com/a/14547223/347216

David Ipsen
fonte
2
Se seu elemento for adicionado / removido do DOM dinamicamente, não se esqueça de assistir seu elemento em um evento '$ destroy' para que você possa desassociar o manipulador. Por exemplo, element.on ('$ destroy', function () {/ * faça o desenlace aqui * /});
Broc.seib 3/09/14
é stop-eventum atributo html? Não consegui encontrá-lo na Mozilla Developer Network ou em qualquer outro lugar.
Ehtesh Choudhury
3
@EhteshChoudhury - "evento de parada" é a nova diretiva que eu criei no snippet JS acima. Confira mais sobre declarando e usando directivas aqui: docs.angularjs.org/guide/directive
David Ipsen
Ótima solução! Na verdade, registrei a angular-eat-eventdiretiva sobre pavilhão, que funciona de maneira semelhante!
gion_13 21/09/2015
parece não funcionar, o ng-click é avaliado antes da diretiva. para que eu possa impedir a execução da diretiva, mas não o contrário.
Rafael
31

Às vezes, pode fazer mais sentido apenas fazer isso:

<widget ng-click="myClickHandler(); $event.stopPropagation()"/>

Eu escolhi fazê-lo dessa maneira, porque não queria myClickHandler()interromper a propagação de eventos nos muitos outros lugares em que ele era usado.

Claro, eu poderia ter adicionado um parâmetro booleano à função manipulador, mas stopPropagation()é muito mais significativo do que apenas true.

Michael Scheper
fonte
2
Eu sempre gostei desta versão, parece a nidificação de elemento não deve relacionadas com a função que eu estou chamando
mcfedr
Eu tive que adicionar $event.stopPropagation()aos elementos internos.
Kishor Pawar
@KishorPawar: Por quê? A maneira como eu fiz isso na resposta não funcionou para você? Caso contrário, seria bom descobrirmos se é devido a uma alteração no funcionamento do Angular ou a um problema no seu código.
Michael Scheper
@MichaelScheper Estou tendo checkboxenvolto em divelemento. Meu divestá tomando 12 colunas e checkboxestá tomando digamos 3 colunas. Eu queria chamar uma função Ano clique de dive função Bno clique de checkbox. então, quando eu estava clicando, as checkboxduas funções estavam sendo invocadas. Quando eu adiciono ng-click="$event.stopPropagation()"a checkbox, eu só tenho funcionam Binvocado.
Kishor Pawar
@KishorPawar: Ah. Sim, eu esperaria isso e, de fato, o objetivo stopPropagation()é interromper a propagação do evento para elementos pai. Eu não sei como você está chamando Buma vez que não está specififed em ng-click, mas o ponto da resposta é que você pode fazer isso: <checkbox ng-click="B(); $event.stopPropagation()">. Você não tem de incluir a stopPropagation()função em B.
Michael Scheper
7

Isso funciona para mim:

<a href="" ng-click="doSomething($event)">Action</a>

this.doSomething = function($event) {
  $event.stopPropagation();
  $event.preventDefault();
};
Andrey Shatilov
fonte
Eu queria lidar com cliques do mouse com ou sem pressionar a tecla Ctrl. Para interromper a seleção (e realçar) dos elementos HTML no navegador (IE 11 no meu caso) quando o usuário clica em vários itens mantendo pressionada a tecla Ctrl, tive que usar o evento ng-mousedown e cancelar o comportamento padrão via $ event .preventDefault ()
pholpar
4

Se você não deseja adicionar a interrupção da propagação a todos os links, isso também funciona. Um pouco mais escalável.

$scope.hideOverlay( $event ){

   // only hide the overlay if we click on the actual div
   if( $event.target.className.indexOf('overlay') ) 
      // hide overlay logic

}
Hampus Ahlgren
fonte
2
Esta solução funciona para o exemplo fornecido (o que é ótimo!), Mas não funciona quando a div externa possui n ou mais elementos aninhados antes do href / ng-click. Então, quando o retorno de chamada hideOverlay () é chamado, o destino é um dos elementos aninhados e não o elemento mais externo com uma diretiva ng-click. Para lidar com elementos aninhados de maneira concebível, é possível usar o jquery para obter os pais e ver se o primeiro pai clicável (a, ng-click etc.) tinha a classe overlay, mas isso parece um pouco complicado ...
Amir
console.log ("potatooverlay" .indexOf ("overlay")! = -1); console.log (/ \ boverlay \ b / g.test ("potatooverlay"));
angular vai deixar você fazer event.target.classList.indexOf('overlay') E esse truque funciona bem mesmo quando o div está aninhado em outros divs
deltree
0

Se você inserir ng-click = "$ event.stopPropagation" no elemento pai do seu modelo, o stopPropogation será capturado à medida que borbulha na árvore, portanto, você deve gravá-lo apenas uma vez para todo o modelo.

tbranch
fonte
0

Você pode registrar outra diretiva em cima da ng-clickqual altera o comportamento padrão ng-clicke para a propagação de eventos. Dessa forma, você não precisaria adicionar $event.stopPropagationà mão.

app.directive('ngClick', function() {
    return {
        restrict: 'A',
        compile: function($element, attr) {
            return function(scope, element, attr) {
                element.on('click', function(event) {
                    event.stopPropagation();
                });
            };
        }
    }
});
bert
fonte
0
<div ng-click="methodName(event)"></div>

Uso do controlador IN

$scope.methodName = function(event) { 
    event.stopPropagation();
}
Dinesh Jain
fonte
Primeiro evento de envio no método de fazê-lo
Dinesh Jain
Sua resposta não adiciona nada ao que foi aceito há 4 anos
Ilya Luzyanin