AngularJS ng-click stopPropagation
Asked Answered
B

5

451

I have a click Event on a table row and in this row there is also a delete Button with a click Event. When i click the delete button the click Event on the row is also fired.

Here is my code.

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

How can I prevent that the showUser Event is fired when i click the delete Button in the table cell?

Borgeson answered 30/11, 2013 at 13:34 Comment(0)
P
840

ngClick directive (as well as all other event directives) creates $event variable which is available on same scope. This variable is a reference to JS event object and can be used to call 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

Parochial answered 30/11, 2013 at 13:52 Comment(9)
I could not figure out if this is available in the controller code - $scope.$event did not seem to work. Any ideas?Demmy
@event object is created inside the ng-click directive, and it is available to you to pass it on to your ng-click handler function: ng-click="deleteUser(user.id, $event)".Parochial
Thanks, kind of figured that one, but I think it still stinks :)Demmy
I think that it's an antipattern to execute multiple expressions like this. Why not just pass $event as a third argument to deleteUser() and then stopPropagation() inside that function?Shanika
@Shanika The multiple expressions do seem clunky, but putting this sort of event code in the controller seems weird. We could solve both problems by putting ng-click="$event.stopPropagation()" on the parent element (the td in this case).Stimulate
I had to add $event.preventDefault() too, otherwise calling $event.stopPropagation() was redirecting me to the root of my app when clicking the button.Zingale
$event.stopPropagation() is the best to stop event bubblingDebit
@DineshJain well, that and event.stopImmediatePropagation() are the only way to stop event bubbling.Psychotechnology
It seems only $event.preventDefault() is needed in this methodVotary
U
136

An addition to Stewie's answer. In case when your callback decides whether the propagation should be stopped or not, I found it useful to pass the $event object to the callback:

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

And then in the callback itself, you can decide whether the propagation of the event should be stopped:

$scope.childHandler = function ($event) {
  if (wanna_stop_it()) {
    $event.stopPropagation();
  }
  ...
};
Uther answered 6/1, 2015 at 18:59 Comment(4)
this is more elegant than execute multiple expressions in html attribute, thanksSkier
Remember to put the '$event' argument, in the html ng-click directive. I stumbled on that at first.Yea
For an immediate stop better use $event.stopImmediatePropagation()Ankeny
So simple, yet so ignored. I was looking at the chosen answer and thinking "really? THIS ugly?". Didn't even realize to pass it as a parameter. Really nice.Propaganda
J
11

I wrote a directive which lets you limit the areas where a click has effect. It could be used for certain scenarios like this one, so instead of having to deal with the click on a case by case basis you can just say "clicks won't come out of this element".

You would use it like this:

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

Keep in mind that this would prevent all clicks on the last cell, not just the button. If that's not what you want you may want to wrap the button like this:

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

Here is the directive's code:

angular.module('awesome', []).directive('isolateClick', function() {
    return {
        link: function(scope, elem) {
            elem.on('click', function(e){
                e.stopPropagation();
            });
        }
   };
});
Jiva answered 12/1, 2016 at 15:1 Comment(4)
Nice! I've renamed your directive to ngClick, as I actually never want to propagate an already handled event.Magda
Be careful with that. Some other plugins might actually want to listen those clicks. If you use tooltips that close when clicking outside they may never close, because the outside clicks are not being propagated to the body.Jiva
That's a good point, but this problem would happen with your directive too. Maybe I should just add a property like ignoreNgClick=true to the event and handle it... somehow. Ideally, in the original ngClick directive, but modifying it sounds dirty.Magda
Yes it does, that's why I wouldn't use this directive unless I really need it. Once I even had to make another directive to continue the click propagation due to this very reason. So I had a an isolate-click directive, then a couple parents up I had another continue-click directive. This way the click was only skipped for the middle elements.Jiva
M
1

In case that you're using a directive like me this is how it works when you need the two data way binding for example after updating an attribute in any model or collection:

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

Now, you can easily use it in any button, link, div, etc. like so:

<button set-survey-in-edition-mode >Edit survey</button>
Malang answered 29/4, 2017 at 19:20 Comment(0)
Y
-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;
      }
   };

Yung answered 16/7, 2015 at 0:56 Comment(2)
Would you add some explanation of why you think this answers the question?Flibbertigibbet
It kind of answers the question. It shows how to pass in the event to the callback which is a more than what the initial question figured out.Abbreviation

© 2022 - 2024 — McMap. All rights reserved.