¿Cuál es la mejor manera de cancelar la propagación de eventos entre llamadas ng-click anidadas?

180

Aquí hay un ejemplo. Digamos que quiero tener una superposición de imágenes como muchos sitios. Por lo tanto, cuando hace clic en una miniatura, aparece una superposición negra sobre toda la ventana y se centra una versión más grande de la imagen. Al hacer clic en la superposición negra, se descarta; Al hacer clic en la imagen, se llamará a una función que muestra la siguiente imagen.

El html:

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

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

El problema es que con esta configuración, si hace clic en la imagen, tanto nextImage()y hideOverlay()recibe una llamada. Pero lo que quiero es que solo nextImage()se me llame.

Sé que puedes capturar y cancelar el evento en la nextImage()función de esta manera:

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

... Pero quiero saber si hay una mejor manera de hacerlo de AngularJS que no requiera que prefije todas las funciones de los elementos dentro de la superposición con este fragmento.

cilphex
fuente
1
return falseal final de la función
Jonathan de M.
1
Creo que esa es la forma de hacerlo onclick, no ng-click.
Michael Scheper

Respuestas:

193

Lo que dijo @JosephSilber, o pasar el objeto $ event a la ng-clickdevolución de llamada y detener la propagación dentro de él:

<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
fuente
8
use $ event.preventDefault (); en v> 1.5
KoolKabin
3
@ KoolKabin Estoy usando Angular 1.5.8 y $ event.stopPropagation () todavía funciona bien. $ event.preventDefault () sin embargo, no funciona
HammerNL
1
Extraño, porque tengo la situación opuesta. stopPropagation ya no funciona, pero preventDefault () sí. La única diferencia es que llamé directamente en ng-click. <tr ng-click = "ctr.foo (); $ event.preventDefault ()"> ..... </tr>
MilitelloVinx
171

Uso $event.stopPropagation():

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

Aquí hay una demostración: http://plnkr.co/edit/3Pp3NFbGxy30srl8OBmQ?p=preview

Joseph Silber
fuente
Me meto ReferenceError: $event is not defineden la consola.
cilphex
Sí, lo hace ... también funciona cuando escribo una versión simple separada desde cero. Estoy tratando de descubrir qué podría hacer que no funcione en mi código de desarrollo. No sé: \
cilphex
@cilphex - ¿Está utilizando una versión actualizada de Angular?
Joseph Silber
Sí, acabo de encontrar el problema. Cuando probé intenté ponerlo $event.stopPropagation()en un lugar donde no funciona. Olvidé eliminarlo. Entonces, incluso cuando lo puse donde funcionó, se lo llamó por segunda vez donde no funcionó. Se deshizo del duplicado disfuncional y es exitoso. Gracias por tu ayuda.
cilphex
101

Me gusta la idea de usar una directiva para esto:

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

Luego use la directiva como:

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

Si lo desea, puede hacer que esta solución sea más genérica como esta respuesta a una pregunta diferente: https://stackoverflow.com/a/14547223/347216

David Ipsen
fuente
2
Si su elemento se agrega / elimina del DOM dinámicamente, no olvide mirar su elemento para un evento '$ destroy' para que pueda desvincular el controlador. Por ejemplo, element.on ('$ destroy', function () {/ * hacer la desvinculación aquí * /});
broc.seib
es stop-eventun atributo html? No pude encontrarlo en Mozilla Developer Network o en ningún otro lugar.
Ehtesh Choudhury
3
@EhteshChoudhury - "stop-event" es la nueva directiva que creé en el fragmento de JS anterior. Vea más sobre declarar y usar directivas aquí: docs.angularjs.org/guide/directive
David Ipsen
Buena solución! De hecho, registré la angular-eat-eventdirectiva sobre Bower, que funciona de manera similar.
gion_13
no parece funcionar, ng-click evalúa antes de la directiva. entonces puedo evitar que la directiva se ejecute pero no al revés.
Rafael
31

A veces, puede tener más sentido hacer esto:

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

Elegí hacerlo de esta manera porque no quería myClickHandler()detener la propagación del evento en los muchos otros lugares donde se usaba.

Claro, yo podría haber añadido un parámetro booleano a la función de controlador, pero stopPropagation()es mucho más significativo que simplemente true.

Michael Scheper
fuente
2
Siempre me gustó esta versión, parece que la anidación del elemento no debería estar relacionada con la función que estoy llamando
mcfedr
Tuve que agregar $event.stopPropagation()elementos internos.
Kishor Pawar
@KishorPawar: ¿Por qué? ¿La forma en que lo hice en la respuesta no funcionó para usted? Si no fuera así, sería bueno para nosotros averiguar si se debe a un cambio en el funcionamiento de Angular o a un problema en su código.
Michael Scheper
@MichaelScheper Estoy checkboxenvuelto en divelemento. Mi divtoma 12 columnas y checkboxtoma 3 columnas. Quería llamar a una función Aal hacer clic divy a la función Bal hacer clic checkbox. así que cuando estaba haciendo clic en checkboxambas funciones se invocaban. Cuando agrego ng-click="$event.stopPropagation()"a checkbox, solo se Binvoca la función .
Kishor Pawar
@KishorPawar: Ah. Sí, esperaría esto, y de hecho, el punto stopPropagation()es detener la propagación del evento a los elementos principales. No estoy seguro de cómo lo está llamando Bya que no está en specififed ng-click, pero el punto de la respuesta es que se puede hacer esto: <checkbox ng-click="B(); $event.stopPropagation()">. No tiene que incluir la stopPropagation()función en B.
Michael Scheper
7

Esto funciona para mi:

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

this.doSomething = function($event) {
  $event.stopPropagation();
  $event.preventDefault();
};
Andrey Shatilov
fuente
Quería manejar los clics del mouse con o sin presionar la tecla Ctrl. Para detener la selección (y resaltar) de elementos HTML en el navegador (IE 11 en mi caso) cuando el usuario hace clic en varios elementos manteniendo presionada la tecla Ctrl, tuve que usar el evento ng-mousedown y cancelar el comportamiento predeterminado a través de $ event .preventDefault ()
pholpar
4

Si no desea tener que agregar la detención de propagación a todos los enlaces, esto también funciona. Un poco más escalable.

$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
fuente
2
Esta solución funciona para el ejemplo proporcionado (¡lo cual es genial!), Sin embargo, no funciona cuando el div externo tiene n o más elementos anidados antes del clic href / ng. Luego, cuando se invoca la devolución de llamada hideOverlay (), el destino es uno de los elementos anidados y no el elemento más externo con una directiva ng-click. Para manejar los elementos anidados concebible que uno podría usar jQuery para llegar a los padres y ver si el primero se puede hacer clic (a, NG-clic, etc.) de los padres tenía la clase de superposición, pero que se parece un poco engorroso ...
Amir
console.log ("potatooverlay" .indexOf ("overlay")! = -1); console.log (/ \ boverlay \ b / g.test ("potatooverlay"));
angular te permitirá hacer event.target.classList.indexOf('overlay') y ese truco funciona bien incluso cuando el div está anidado en otros divs
deltree
0

Si inserta ng-click = "$ event.stopPropagation" en el elemento principal de su plantilla, se detendrá stopPropogation a medida que burbujea en el árbol, por lo que solo tiene que escribirlo una vez para toda la plantilla.

tbranch
fuente
0

Puede registrar otra directiva además de la ng-clickcual modifica el comportamiento predeterminado ng-clicky detiene la propagación del evento. De esta manera no tendría que agregar $event.stopPropagationa mano.

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

Uso del controlador IN

$scope.methodName = function(event) { 
    event.stopPropagation();
}
Dinesh Jain
fuente
Primer evento de envío en el método para hacerlo
Dinesh Jain
Su respuesta no agrega nada al aceptado hace 4 años
Ilya Luzyanin