Quelle est la meilleure façon d'annuler la propagation d'événements entre les appels ng-click imbriqués?

180

Voici un exemple. Disons que je veux avoir une superposition d'image comme beaucoup de sites. Ainsi, lorsque vous cliquez sur une vignette, une superposition noire apparaît sur toute votre fenêtre et une version plus grande de l'image y est centrée. Cliquer sur la superposition noire la rejette; cliquer sur l'image appellera une fonction qui affiche l'image suivante.

Le html:

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

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

Le problème est qu'avec cette configuration, si vous cliquez sur l'image, les deux nextImage()et hideOverlay()sont appelés. Mais ce que je veux, c'est seulement nextImage()être appelé.

Je sais que vous pouvez capturer et annuler l'événement dans la nextImage()fonction comme ceci:

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

... Mais je veux savoir s'il existe une meilleure façon AngularJS de le faire qui ne m'obligera pas à préfixer toutes les fonctions sur les éléments à l'intérieur de la superposition avec cet extrait de code.

cilphex
la source
1
return falseà la fin de la fonction
Jonathan de M.
1
Je crois que c'est la façon de le faire onclick, non ng-click.
Michael Scheper

Réponses:

193

Ce que @JosephSilber a dit, ou passez l'objet $ event en ng-clickcallback et arrêtez la propagation à l'intérieur de celui-ci:

<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
la source
8
utilisez $ event.preventDefault (); en v> 1.5
KoolKabin
3
@KoolKabin J'utilise Angular 1.5.8 et $ event.stopPropagation () fonctionne toujours bien. $ event.preventDefault () cependant, ne fonctionne pas
HammerNL
1
Bizarre, parce que j'ai la situation inverse. stopPropagation ne fonctionne plus, mais preventDefault () le fait. La seule différence est que j'ai appelé directement sur ng-click. <tr ng-click = "ctr.foo (); $ event.preventDefault ()"> ..... </tr>
MilitelloVinx
171

Utilisez $event.stopPropagation():

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

Voici une démo: http://plnkr.co/edit/3Pp3NFbGxy30srl8OBmQ?p=preview

Joseph Silber
la source
J'entre ReferenceError: $event is not defineddans la console.
cilphex
Oui, ça marche ... ça marche aussi quand j'écris une version simple séparée à partir de zéro. J'essaie de comprendre ce qui pourrait empêcher cela de fonctionner dans mon code de développement. Dunno: \
cilphex
@cilphex - Utilisez-vous une version à jour d'Angular?
Joseph Silber
Oui - vient de trouver le problème. Lors du test, j'ai essayé de mettre $event.stopPropagation()dans un endroit où cela ne fonctionne pas. J'ai oublié de le supprimer. Donc, même quand je l'ai mis là où ça fonctionnait, on l'appelait une deuxième fois là où ça ne fonctionnait pas. Débarrassez-vous du double dysfonctionnel et c'est réussi. Merci de votre aide.
cilphex
101

J'aime l'idée d'utiliser une directive pour cela:

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

Ensuite, utilisez la directive comme:

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

Si vous le souhaitez, vous pouvez rendre cette solution plus générique comme cette réponse à une autre question: https://stackoverflow.com/a/14547223/347216

David Ipsen
la source
2
Si votre élément est ajouté / supprimé du DOM dynamiquement, n'oubliez pas de surveiller votre élément pour un événement '$ destroy' afin que vous puissiez dissocier le gestionnaire. Par exemple, element.on ('$ destroy', function () {/ * fait la dissociation ici * /});
broc.seib
est stop-eventun attribut html? Je n'ai pas pu le trouver sur Mozilla Developer Network ou ailleurs.
Ehtesh Choudhury
3
@EhteshChoudhury - "stop-event" est la nouvelle directive que j'ai créée dans l'extrait JS ci-dessus. Pour en savoir plus sur la déclaration et l'utilisation des directives, cliquez ici: docs.angularjs.org/guide/directive
David Ipsen
Belle solution! J'ai en fait enregistré la angular-eat-eventdirective sur le bower, qui fonctionne de manière similaire!
gion_13
cela ne semble pas fonctionner, ng-click évalue avant la directive. donc je peux empêcher la directive de s'exécuter mais pas l'inverse.
Rafael le
31

Parfois, il peut être plus judicieux de faire ceci:

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

J'ai choisi de le faire de cette façon parce que je ne voulais myClickHandler()pas arrêter la propagation de l'événement dans les nombreux autres endroits où il était utilisé.

Bien sûr, j'aurais pu ajouter un paramètre booléen à la fonction de gestionnaire, mais stopPropagation()c'est beaucoup plus significatif que juste true.

Michael Scheper
la source
2
J'ai toujours aimé cette version, il semble que l'imbrication de l'élément ne devrait pas être liée à la fonction que
j'appelle
J'ai dû ajouter $event.stopPropagation()des éléments internes.
Kishor Pawar
@KishorPawar: Pourquoi? Est-ce que la façon dont je l'ai fait dans la réponse n'a pas fonctionné pour vous? Si ce n'est pas le cas, il serait bon pour nous de savoir si c'est à cause d'un changement dans le fonctionnement d'Angular ou d'un problème dans votre code.
Michael Scheper
@MichaelScheper J'ai checkboxenveloppé d' divélément. Mon divprend 12 colonnes et checkboxprend par exemple 3 colonnes. Je voulais appeler une fonction Aen un clic divet une fonction Ben un clic checkbox. donc quand je cliquais, les checkboxdeux fonctions étaient appelées. Quand j'ajoute ng-click="$event.stopPropagation()"à checkbox, je n'ai qu'une fonction Binvoquée.
Kishor Pawar
@KishorPawar: Ah. Oui, je m'attendrais à cela, et en effet, le but stopPropagation()est d'arrêter la propagation de l'événement aux éléments parents. Je ne sais pas comment vous appelez Bcar il est pas specififed dans ng-click, mais le point de la réponse est que vous pouvez le faire: <checkbox ng-click="B(); $event.stopPropagation()">. Vous n'êtes pas obligé d'inclure la stopPropagation()fonction dans B.
Michael Scheper
7

Cela fonctionne pour moi:

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

this.doSomething = function($event) {
  $event.stopPropagation();
  $event.preventDefault();
};
Andrey Shatilov
la source
Je voulais gérer les clics de souris avec ou sans appuyer sur la touche Ctrl. Pour arrêter la sélection (et mettre en évidence) des éléments HTML dans le navigateur (IE 11 dans mon cas) lorsque l'utilisateur clique sur différents éléments en maintenant la touche Ctrl enfoncée, j'ai dû utiliser l'événement ng-mousedown et annuler le comportement par défaut via $ event .preventDefault ()
pholpar
4

Si vous ne voulez pas avoir à ajouter la propagation d'arrêt à tous les liens, cela fonctionne également. Un peu plus évolutif.

$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
la source
2
Cette solution fonctionne pour l'exemple fourni (ce qui est génial!), Mais elle ne fonctionne pas lorsque la div externe a n ou plusieurs éléments imbriqués avant le href / ng-click. Ensuite, lorsque le rappel hideOverlay () est appelé, la cible est l'un des éléments imbriqués et non l'élément le plus extérieur avec une directive ng-click. Pour gérer les éléments imbriqués, on pourrait utiliser jquery pour obtenir les parents et voir si le premier parent cliquable (a, ng-click, etc.) avait la classe de superposition, mais cela semble un peu encombrant ...
Amir
console.log ("potatooverlay" .indexOf ("overlay")! = -1); console.log (/ \ boverlay \ b / g.test ("potatooverlay"));
angular vous laissera faire event.target.classList.indexOf('overlay') Et cette astuce fonctionne bien même lorsque le div est imbriqué dans d'autres divs
deltree
0

Si vous insérez ng-click = "$ event.stopPropagation" sur l'élément parent de votre modèle, stopPropogation sera intercepté au fur et à mesure qu'il remontera dans l'arborescence, vous n'aurez donc à l'écrire qu'une seule fois pour tout votre modèle.

tbranch
la source
0

Vous pouvez enregistrer une autre directive en plus de ng-clicklaquelle modifie le comportement par défaut de ng-clicket arrête la propagation de l'événement. De cette façon, vous n'aurez pas à ajouter $event.stopPropagationmanuellement.

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

Utilisation du contrôleur IN

$scope.methodName = function(event) { 
    event.stopPropagation();
}
Dinesh Jain
la source
Premier événement d'envoi dans la méthode pour le faire
Dinesh Jain
Votre réponse n'ajoute rien à celle acceptée il y a 4 ans
Ilya Luzyanin