AngularJS ng-click stopPropagation

433

Tengo un evento de clic en una fila de la tabla y en esta fila también hay un botón Eliminar con un evento de clic. Cuando hago clic en el botón Eliminar, también se activa el evento de clic en la fila.

Aquí está mi 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>

¿Cómo puedo evitar que showUserse active el evento cuando hago clic en el botón Eliminar en la celda de la tabla?

michael_knight
fuente

Respuestas:

801

La directiva ngClick (así como todas las demás directivas de eventos) crea una $eventvariable que está disponible en el mismo ámbito. Esta variable es una referencia al eventobjeto JS y se puede usar para llamar 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
fuente
2
No pude averiguar si esto está disponible en el código del controlador - $ scope. $ Event no parecía funcionar. ¿Algunas ideas?
user1338062
93
@event objeto se crea dentro de la directiva ng-clic, y es a su disposición para dárselo a su ng-clickfunción de controlador: ng-click="deleteUser(user.id, $event)".
Stewie
66
Gracias, pensé en eso, pero creo que todavía apesta :)
user1338062
2
Creo que es un antipatrón ejecutar múltiples expresiones como esta. ¿Por qué no simplemente pasar $ event como tercer argumento para deleteUser () y luego stopPropagation () dentro de esa función?
djheru
18
También tuve que agregar $event.preventDefault(), de lo contrario, llamar $event.stopPropagation()me estaba redirigiendo a la raíz de mi aplicación al hacer clic en el botón.
Desty
124

Una adición a la respuesta de Stewie. En caso de que su devolución de llamada decida si la propagación se debe detener o no, me pareció útil pasar el $eventobjeto a la devolución de llamada:

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

Y luego, en la devolución de llamada, puede decidir si se debe detener la propagación del evento:

$scope.childHandler = function ($event) {
  if (wanna_stop_it()) {
    $event.stopPropagation();
  }
  ...
};
korya
fuente
10
esto es más elegante que ejecutar múltiples expresiones en el atributo html, gracias
Eason
3
Recuerde poner el argumento '$ evento', en la directiva html ng-click. Me topé con eso al principio.
ThinkBonobo
1
Para una parada inmediata mejor use $ event.stopImmediatePropagation ()
Edgar Chavolla
Tan simple, pero tan ignorado. Estaba mirando la respuesta elegida y pensando "¿en serio? ¿ESTA fea?". Ni siquiera me di cuenta de pasarlo como parámetro. Muy agradable.
tfrascaroli
10

Escribí una directiva que le permite limitar las áreas donde un clic tiene efecto. Podría usarse para ciertos escenarios como este, por lo que en lugar de tener que lidiar con el clic caso por caso, puede decir "los clics no saldrán de este elemento".

Lo usarías así:

<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>

Tenga en cuenta que esto evitaría todos los clics en la última celda, no solo el botón. Si eso no es lo que desea, puede ajustar el botón de esta manera:

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

Aquí está el código de la directiva:

angular.module('awesome', []).directive('isolateClick', function() {
    return {
        link: function(scope, elem) {
            elem.on('click', function(e){
                e.stopPropagation();
            });
        }
   };
});
Jens
fuente
¡Agradable! He cambiado el nombre de su directiva a ngClick, ya que en realidad nunca quiero propagar un evento ya manejado.
maaartinus
1
Ten cuidado con eso. Es posible que algunos otros complementos realmente quieran escuchar esos clics. Si usa información sobre herramientas que se cierran al hacer clic afuera, es posible que nunca se cierren, porque los clics externos no se propagan al cuerpo.
Jens
Ese es un buen punto, pero este problema también ocurriría con su directiva. Tal vez debería agregar una propiedad como ignoreNgClick=trueal evento y manejarlo ... de alguna manera. Idealmente, en la ngClickdirectiva original , pero modificarlo suena sucio.
maaartinus
Sí, por eso no usaría esta directiva a menos que realmente la necesite. Una vez incluso tuve que hacer otra directiva para continuar la propagación de clics debido a esta misma razón. Así que tenía una directiva de clic aislado, luego un par de padres arriba tenía otra directiva de clic continuo. De esta manera, el clic solo se omitió para los elementos intermedios.
Jens
1

En caso de que esté utilizando una directiva como yo, así es como funciona cuando necesita el enlace de dos vías de datos, por ejemplo, después de actualizar un atributo en cualquier modelo o colección:

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

Ahora, puede usarlo fácilmente en cualquier botón, enlace, div, etc., así:

<button set-survey-in-edition-mode >Edit survey</button>
heriberto perez
fuente
-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;
      }
   };

Dobladillos
fuente
77
¿Agregarías alguna explicación de por qué crees que esto responde la pregunta?
paisanco
De alguna manera responde la pregunta. Muestra cómo pasar el evento a la devolución de llamada, que es más de lo que la pregunta inicial descubrió.
Hewstone