What's the best way to cancel event propagation between nested ng-click calls?
Asked Answered
A

10

188

Here's an example. Let's say I want to have an image overlay like a lot of sites. So when you click a thumbnail, a black overlay appears over your whole window, and a larger version of the image is centered in it. Clicking the black overlay dismisses it; clicking the image will call a function that shows the next image.

The html:

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

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

The problem is that with this setup, if you click the image, both nextImage() and hideOverlay() are called. But what I want is for only nextImage() to be called.

I know you can capture and cancel the event in the nextImage() function like this:

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

...But I want to know if there's a better AngularJS way of doing it that won't require me to prefix all of the functions on elements inside the overlay with this snippet.

Anglo answered 4/3, 2013 at 2:15 Comment(2)
return false at the end of the functionHierodule
I believe that's the way to do it for onclick, not ng-click.Freesia
V
200

What @JosephSilber said, or pass the $event object into ng-click callback and stop the propagation inside of it:

<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
}
Voiceful answered 4/3, 2013 at 2:30 Comment(3)
use $event.preventDefault(); in v > 1.5Acrylonitrile
@Acrylonitrile I'm using Angular 1.5.8 and $event.stopPropagation() still works fine. $event.preventDefault() however, does not workRadiobroadcast
Weird, because I have the opposite situation. stopPropagation don't work anymore, but preventDefault() does. Only difference is that I called directly on ng-click. <tr ng-click="ctr.foo(); $event.preventDefault()">.....</tr>Ignatius
S
174

Use $event.stopPropagation():

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

Here's a demo: http://plnkr.co/edit/3Pp3NFbGxy30srl8OBmQ?p=preview

Scheck answered 4/3, 2013 at 2:19 Comment(4)
I get ReferenceError: $event is not defined in the console.Anglo
Yes, it does... it also works when I write a separate simple version from scratch. I'm trying to figure out what could make it not work in my development code. Dunno :\Anglo
@Anglo - Are you using an up-to-date version of Angular?Scheck
Yes - just found the problem. When testing I tried putting $event.stopPropagation() in a place where it doesn't work. Forgot to remove it. So even when I put it where it did work, it was being called a second time where it didn't. Got rid of the dysfunctional duplicate and it's successful. Thanks for your help.Anglo
A
101

I like the idea of using a directive for this:

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

Then use the directive like:

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

If you wanted, you could make this solution more generic like this answer to a different question: https://mcmap.net/q/137089/-how-can-i-make-an-angularjs-directive-to-stoppropagation

Abrams answered 7/11, 2013 at 20:52 Comment(5)
If your element is added/removed from the DOM dynamically, don't forget to watch your element for a '$destroy' event so you can unbind the handler. E.g., element.on('$destroy', function() { /* do the unbinding here */ });Ruelas
is stop-event an html attribute? I couldn't find it on Mozilla Developer Network or anywhere else.Shanika
@EhteshChoudhury - "stop-event" is the new directive I created in the JS snippet above. Check out more about declaring and using directives here: docs.angularjs.org/guide/directiveAbrams
Nice solution! I actually registered the angular-eat-event directive on bower, which works in a similar way!Outfitter
it doesn't seem to work, ng-click evaluates before the directive. so I can prevent directive from executing but not the other way around.Southward
F
32

Sometimes, it may make most sense just to do this:

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

I chose to do it this way because I didn't want myClickHandler() to stop the event propagation in the many other places it was used.

Sure, I could've added a boolean parameter to the handler function, but stopPropagation() is much more meaningful than just true.

Freesia answered 14/8, 2014 at 5:40 Comment(5)
I always liked this version, seems the nesting of element shouldn't related to the function that i am callingTonneau
I had to add $event.stopPropagation() to internal elements.Gest
@KishorPawar: Why? Did the way I did it in the answer not work for you? If it didn't, it'd be good for us to find out whether it's because of a change in the way Angular works, or a problem in your code.Freesia
@MichaelScheper I am having checkbox wrapped in div element. My div is taking 12 columns and checkbox is taking say 3 columns. I wanted to call a function A on click of div and function B on click of checkbox. so when I was clicking checkbox both functions were getting invoked. When I add ng-click="$event.stopPropagation()" to checkbox, I got only function B invoked.Gest
@KishorPawar: Ah. Yes, I would expect this, and indeed, the point of stopPropagation() is to stop the event propagating to parent elements. I'm not sure how you're calling B since it's not specififed in ng-click, but the point of the answer is that you can do this: <checkbox ng-click="B(); $event.stopPropagation()">. You don't have to include the stopPropagation() function in B.Freesia
E
9

This works for me:

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

this.doSomething = function($event) {
  $event.stopPropagation();
  $event.preventDefault();
};
Enact answered 1/9, 2016 at 8:48 Comment(1)
I wanted to handle mouse clicks with or without pressing the Ctrl key. To stop selection (and highlight) of HTML elements in the browser (IE 11 in my case) when the user clicks various items with holding down the Ctrl key, I had to use the ng-mousedown event and cancelling the default behavior via $event.preventDefault()Caveat
R
4

If you don't want to have to add the stop propagation to all links this works as well. A bit more scalable.

$scope.hideOverlay( $event ){

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

}
Revivify answered 19/9, 2013 at 11:15 Comment(3)
This solution works for the provided example (which is great!), however it doesn't work when the outer div has n or more nested elements prior to the href / ng-click. Then when the hideOverlay() callback is invoked the target is one of the nested elements and not the outermost element with an ng-click directive. To handle nested elements conceivably one could use jquery to get the parents and see if the first clickable (a, ng-click, etc.) parent had the overlay class, but that looks a bit cumbersome...Cauley
console.log("potatooverlay".indexOf("overlay") != -1); console.log(/\boverlay\b/g.test("potatooverlay"));Stillmann
angular will let you do event.target.classList.indexOf('overlay') And that trick works fine even when the div is nested in other divsDance
F
0

If you insert ng-click="$event.stopPropagation" on the parent element of your template, the stopPropogation will be caught as it bubbles up the tree, so you only have to write it once for your entire template.

Fastigiate answered 21/4, 2016 at 2:56 Comment(0)
V
0

You can register another directive on top of ng-click which amends the default behaviour of ng-click and stops the event propagation. This way you wouldn't have to add $event.stopPropagation by hand.

app.directive('ngClick', function() {
    return {
        restrict: 'A',
        compile: function($element, attr) {
            return function(scope, element, attr) {
                element.on('click', function(event) {
                    event.stopPropagation();
                });
            };
        }
    }
});
Vengeance answered 22/7, 2016 at 9:41 Comment(0)
P
0
<div ng-click="methodName(event)"></div>

IN controller use

$scope.methodName = function(event) { 
    event.stopPropagation();
}
Protanopia answered 7/6, 2017 at 2:41 Comment(2)
First send event in the method to do itProtanopia
Your answer adds nothing to the accepted one 4 years agoThereinto
H
0

In my case event.stopPropagation(); was making my page refresh each time I pressed on a link so I had to find another solution.

So what I did was to catch the event on the parent and block the trigger if it was actually coming from his child using event.target.

Here is the solution:

if (!angular.element($event.target).hasClass('some-unique-class-from-your-child')) ...

So basically your ng-click from your parent component works only if you clicked on the parent. If you clicked on the child it won't pass this condition and it won't continue it's flow.

Herwig answered 27/10, 2020 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.