What's the best way to cancel event propagation between nested ng-click calls?
AngularjsAngularjs Problem Overview
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.
Angularjs Solutions
Solution 1 - Angularjs
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
}
Solution 2 - Angularjs
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
Solution 3 - Angularjs
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://stackoverflow.com/a/14547223/347216
Solution 4 - Angularjs
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
.
Solution 5 - Angularjs
This works for me:
<a href="" ng-click="doSomething($event)">Action</a>
this.doSomething = function($event) {
$event.stopPropagation();
$event.preventDefault();
};
Solution 6 - Angularjs
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
}
Solution 7 - Angularjs
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.
Solution 8 - Angularjs
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();
});
};
}
}
});
Solution 9 - Angularjs
<div ng-click="methodName(event)"></div>
IN controller use
$scope.methodName = function(event) {
event.stopPropagation();
}
Solution 10 - Angularjs
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.