ChatGPT解决这个技术问题 Extra ChatGPT

AngularJS ng-click stopPropagation

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?


B
Brendan Moore

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


I could not figure out if this is available in the controller code - $scope.$event did not seem to work. Any ideas?
@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)".
Thanks, kind of figured that one, but I think it still stinks :)
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?
I had to add $event.preventDefault() too, otherwise calling $event.stopPropagation() was redirecting me to the root of my app when clicking the button.
T
Timo Tijhof

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();
  }
  ...
};

this is more elegant than execute multiple expressions in html attribute, thanks
Remember to put the '$event' argument, in the html ng-click directive. I stumbled on that at first.
For an immediate stop better use $event.stopImmediatePropagation()
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.
J
Jens

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

Nice! I've renamed your directive to ngClick, as I actually never want to propagate an already handled event.
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.
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.
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.
H
Heriberto Magaña

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>

H
Hems
<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;
      }
   };


Would you add some explanation of why you think this answers the question?
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.