AngularJS ng-click stopPropagation

433

Eu tenho um evento de clique em uma linha da tabela e nessa linha também há um botão de exclusão com um evento de clique. Quando clico no botão Excluir, o clique em Evento na linha também é acionado.

Aqui está o meu código.

<tbody>
  <tr ng-repeat="user in users" class="repeat-animation" ng-click="showUser(user, $index)">
    <td>{{user.firstname}}</td>
    <td>{{user.lastname}}</td>
    <td>{{user.email}}</td>
    <td><button class="btn red btn-sm" ng-click="deleteUser(user.id, $index)">Delete</button></td>
  </tr>
</tbody>

Como impedir que o showUserevento seja acionado quando clico no botão excluir na célula da tabela?

michael_knight
fonte

Respostas:

801

A diretiva ngClick (assim como todas as outras diretivas de eventos) cria uma $eventvariável que está disponível no mesmo escopo. Esta variável é uma referência ao eventobjeto JS e pode ser usada para chamar stopPropagation():

<table>
  <tr ng-repeat="user in users" ng-click="showUser(user)">
    <td>{{user.firstname}}</td>
    <td>{{user.lastname}}</td>
    <td>
      <button class="btn" ng-click="deleteUser(user.id, $index); $event.stopPropagation();">
        Delete
      </button>
    </td>              
  </tr>
</table>

PLUNKER

Stewie
fonte
2
Não consegui descobrir se isso está disponível no código do controlador - evento $ scope. $ Parece não funcionar. Alguma ideia?
user1338062
93
objeto @event é criado dentro da directiva ng-click, e ele está disponível para você para passá-lo para a sua ng-clickfunção de manipulador: ng-click="deleteUser(user.id, $event)".
21414 Stewie
6
Obrigado, meio que percebi que um, mas eu acho que ainda fede :)
user1338062
2
Eu acho que é um antipadrão para executar várias expressões como esta. Por que não passar $ event como terceiro argumento para deleteUser () e stopPropagation () dentro dessa função?
djheru
18
Eu também precisava adicionar $event.preventDefault(), caso contrário, a chamada $event.stopPropagation()estava me redirecionando para a raiz do meu aplicativo ao clicar no botão.
Desty
124

Uma adição à resposta de Stewie. Caso seu retorno de chamada decida se a propagação deve ou não ser interrompida, achei útil passar o $eventobjeto para o retorno de chamada:

<div ng-click="parentHandler($event)">
  <div ng-click="childHandler($event)">
  </div>
</div>

E então, no próprio retorno de chamada, você pode decidir se a propagação do evento deve ser interrompida:

$scope.childHandler = function ($event) {
  if (wanna_stop_it()) {
    $event.stopPropagation();
  }
  ...
};
korya
fonte
10
este é mais elegante do que executar múltiplas expressões no atributo html, graças
Eason
3
Lembre-se de colocar o argumento '$ event' na diretiva html ng-click. Eu tropecei nisso no começo.
ThinkBonobo
1
Para uma parada imediata melhor usar $ Event.stopImmediatePropagation ()
Edgar Chavolla
Tão simples, mas tão ignorado. Eu estava olhando para a resposta escolhida e pensando "realmente? Isso é feio?". Nem percebeu passar isso como parâmetro. Muito legal.
Tfrascaroli
10

Eu escrevi uma diretiva que permite limitar as áreas em que um clique tem efeito. Pode ser usado para certos cenários como este, então, em vez de precisar lidar com o clique caso a caso, basta dizer "os cliques não sairão desse elemento".

Você usaria assim:

<table>
  <tr ng-repeat="user in users" ng-click="showUser(user)">
    <td>{{user.firstname}}</td>
    <td>{{user.lastname}}</td>
    <td isolate-click>
      <button class="btn" ng-click="deleteUser(user.id, $index);">
        Delete
      </button>
    </td>              
  </tr>
</table>

Lembre-se de que isso impediria todos os cliques na última célula, não apenas o botão. Se não é isso que você deseja, você pode apertar o botão assim:

<span isolate-click>
    <button class="btn" ng-click="deleteUser(user.id, $index);">
        Delete
    </button>
</span>

Aqui está o código da diretiva:

angular.module('awesome', []).directive('isolateClick', function() {
    return {
        link: function(scope, elem) {
            elem.on('click', function(e){
                e.stopPropagation();
            });
        }
   };
});
Jens
fonte
Agradável! Renomeei sua diretiva para ngClick, pois na verdade nunca quero propagar um evento já tratado.
maaartinus
1
Tenha cuidado com isso. Alguns outros plugins podem realmente querer ouvir esses cliques. Se você usar dicas de ferramentas que fecham ao clicar fora, elas nunca podem fechar, porque os cliques externos não estão sendo propagados para o corpo.
Jens
Esse é um bom ponto, mas esse problema também aconteceria com a sua diretiva. Talvez eu deva adicionar uma propriedade como ignoreNgClick=trueao evento e manipulá-la ... de alguma forma. Idealmente, na ngClickdiretiva original , mas modificá-lo parece sujo.
Maaartinus
Sim, é por isso que eu não usaria essa diretiva, a menos que eu realmente precisasse. Uma vez eu tive que fazer outra diretiva para continuar a propagação de cliques por esse motivo. Então, eu tinha uma diretiva de isolate-click; depois, alguns pais tinham outra diretiva de continue-click. Dessa forma, o clique foi pulado apenas para os elementos do meio.
Jens
1

Caso você esteja usando uma diretiva como eu, é assim que funciona quando você precisa da ligação de duas vias de dados, por exemplo, após atualizar um atributo em qualquer modelo ou coleção:

angular.module('yourApp').directive('setSurveyInEditionMode', setSurveyInEditionMode)

function setSurveyInEditionMode() {
  return {
    restrict: 'A',
    link: function(scope, element, $attributes) {
      element.on('click', function(event){
        event.stopPropagation();
        // In order to work with stopPropagation and two data way binding
        // if you don't use scope.$apply in my case the model is not updated in the view when I click on the element that has my directive
        scope.$apply(function () {
          scope.mySurvey.inEditionMode = true;
          console.log('inside the directive')
        });
      });
    }
  }
}

Agora, você pode usá-lo facilmente em qualquer botão, link, div, etc., assim:

<button set-survey-in-edition-mode >Edit survey</button>
heriberto perez
fonte
-14
<ul class="col col-double clearfix">
 <li class="col__item" ng-repeat="location in searchLocations">
   <label>
    <input type="checkbox" ng-click="onLocationSelectionClicked($event)" checklist-model="selectedAuctions.locations" checklist-value="location.code" checklist-change="auctionSelectionChanged()" id="{{location.code}}"> {{location.displayName}}
   </label>



$scope.onLocationSelectionClicked = function($event) {
      if($scope.limitSelectionCountTo &&         $scope.selectedAuctions.locations.length == $scope.limitSelectionCountTo) {
         $event.currentTarget.checked=false;
      }
   };

Hems
fonte
7
Você gostaria de adicionar uma explicação de por que você acha que isso responde à pergunta?
paisanco 16/07/2015
Isso meio que responde a pergunta. Ele mostra como passar o evento para o retorno de chamada, que é mais do que o que a pergunta inicial descobriu.
hewstone