0

I have a directive disable-ng-clicks and under certain conditions, I want to prevent all ng-clicks that are children of the directive. Here is some example markup:

<div disable-ng-clicks> <!-- directive -->
  <a ng-click="someAction()"></a>
  <div ng-controller="myController">
    <a ng-click="anotherAction()"></a>
    <a ng-click="moreActions()"></a>
  </div>
</div>

If these were normal hyperlinks, I could do something like this in the link function:

function(scope, iElement, iAttrs) {
  var ngClicks = angular.element(iElement[0].querySelectorAll('[ng-click]'));
  ngClicks.on('click', function(event) {
    if(trigger) { // a dynamic variable that triggers disabling the clicks
      event.preventDefault();
    }
  });
}

But this does not work for ng-click directives. Is there another way to accomplish this?

thetallweeks
  • 6,875
  • 6
  • 25
  • 24
  • 1
    Well the element that you are clicking isn't the div, is the anchor. You should put this in every anchor or change the directive to disable ng-click for child elements. The way you are doing It will work only for the element that has the directive. – Fals May 05 '14 at 20:25
  • I'm not sure I understand. I can attach the click handler onto the parent and it it executes when I click on a child. – thetallweeks May 05 '14 at 20:35
  • Yes, you can do this, but you will need to change the directive to look for every chield from the element that has It. – Fals May 05 '14 at 20:39
  • Ahh I see what you are saying. I updated the question. – thetallweeks May 05 '14 at 23:54

4 Answers4

1

Here is the best I could come up with. I created a new directive to replace ng-click:

 directive('myClick', ['$parse', function($parse) {
  return {
    restrict: 'A',
    compile: function($element, attrs) {
      var fn = $parse(attrs.myClick);
      return function (scope, element, attrs) {
        var disabled = false;
        scope.$on('disableClickEvents', function () {
          disabled = true;
        });
        scope.$on('enableClickEvents', function () {
          disabled = false;
        });
        element.on('click', function (event) {
          if (!disabled) {
            scope.$apply(function () {
              fn(scope, { $event: event });
            });
          }
        });
      };
    }
  }
}]);

So in a different directive, I can have:

if (condition) { 
  scope.$broadcast('disableClickEvents');
}

and when I want to re-enable:

if (otherCondition) { 
  scope.$broadcast('enableClickEvents');
}

I don't like having to use a different directive for ng-click, but this is the best plan I could think of.

thetallweeks
  • 6,875
  • 6
  • 25
  • 24
0

You are catching 'click' event on parent only because of JS events bubbling, so if you want to intercept it on all descendants, so your directive should get all descendants of current element, listen their 'click' event and prevent it if necessary.

OZ_
  • 12,492
  • 7
  • 50
  • 68
  • Can you post example code? I can grab all descendants with ng-click, but even if I try to run `event.preventDefault()`, it occurs too late because Angular's ng-click has already taken over as the click handler. – thetallweeks May 05 '14 at 21:22
  • An ng-click event is not a default action, which is what event.preventDefault() prevents. Instead, you need to unregister click events -- take a look at my answer for an example directive. – Marc Kline May 05 '14 at 22:54
  • I updated the question, that was a mistake on my part. – thetallweeks May 05 '14 at 23:55
0

This directive will iterate over all child elements, check to see if they have an ng-click attribute, and if they do, it will disable any registered click event handlers:

directive('disableNgClicks', function(){
  return {
    restrict: 'E',
    link: function(scope, elem, attrs){
      angular.forEach(elem.children(), function(childElem) {
        if (childElem.outerHTML.indexOf("ng-click") > -1) {
          angular.element(childElem).off('click');
        }
      });
    }
  }
})

Plunker demo

Marc Kline
  • 9,399
  • 1
  • 33
  • 36
  • Instead of unregistering the click events, I would rather `stopImmediatePropagation` because I want to toggle this on and off. Unfortunately, I am not able to get my directive to execute its click handler before ng-click. I have found that [priority](http://stackoverflow.com/questions/16208899/intercepting-ng-click-in-angularjs) dictates order when two directives are on the same element, but when one is the parent, I'm not sure how to control which click handler fires first. – thetallweeks May 05 '14 at 23:51
  • Yes, it's easy enough to prevent the ng-click event on a per-element basis, but I don't see any obvious way to override their behavior using a parent directive while allowing for disabling. The ordering is a real issue, as you've found. I think that your best bet is to use a scope variable that prevents the ng-click handler from being called when it is set to true: http://stackoverflow.com/questions/14545744/how-to-make-a-condition-on-ng-click-in-angularjs – Marc Kline May 06 '14 at 04:57
  • Small comment. The directive has to be restrict: 'A' to work with @thetallweeks code. – Daniel Falabella Oct 11 '17 at 15:36
0

I know this is 2 years ago but I needed to do something similar and came up with a rather simple solution.

The object:

items: {
    item1 : {
        selected: 0,
        other: 'stuff'
    },
    item2 : {
        selected : 1,
        other: 'stuff'
    }  
}

The HTML:

<div ng-repeat="item in items" ng-model="item.selected" ng-click="selectParent($event)">
    <div ng-click="item.selected ? selectChild($event) : null">Child</div>
</div>

The functions:

$scope.selectParent = function($event) {
    var itemScope = angular.element($event.currentTarget)scope().item;
    itemScope.selected = !itemScope.selected;
}

$scope.selectChild = function($event) {
    $event.stopPropagation;
    console.log('I only get triggered if parent item is selected');
}

This is a pretty raw example of what I did. You should probably be using a directive that gives you $scope rather than angular.element($event.currentTarget).scope... either way the simplistic inline if logic is what I was really getting at. You can call a function or not based on some value.