Join the Stack Overflow Community
Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

I'm trying to add fields dynamically and I'm binding click event in the directive's link function. But it seems to be firing several times as I add more fields. See the example below -

var clicks = 0;
var app = angular.module('test', []);

app.directive('control', function($compile) {
  var linker = function(scope, element, attrs) {
    element.html('<button style="background:grey;">Button ' + scope.count + '</button>');

    element.bind('click', function() {
      clicks++;
      $('#clicks').html('Clicked ' + clicks + ' times')
    });

    $compile(element.contents())(scope);
  };

  return {
    restrict: 'E',
    scope: {
      count: '@'
    },
    link: linker
  }
});

app.controller('TestController', function($scope, $compile) {
  $scope.count = 1;
  $scope.addControl = function() {
    $('#content').append('<control count="' + $scope.count+++'"></control>');
    $compile($('#content').contents())($scope);
  };
});
#content {
  margin-top: 10px;
}
#clicks {
  margin-top: 10px;
}
p {
  color: grey;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="TestController">
  <button ng-click="addControl()">Add Button</button>
  <div id="content"></div>
  <div id="clicks"></div>
</div>

<p>Add multiple buttons and click the button that was added in the beginning. Notice the Clicks to be increased multiple times.</p>
<p>For instance, add a bunch of buttons and click Button 1</p>

I'd like to have the click event fired only once for a specific element.

share|improve this question
up vote 2 down vote accepted

The problem is because you are compile #content DOM multiple times, Because of that your old elements are again get binded click event to it. If you look closely you will see that you nth button is having n-1 click event binded to it.

Below is explanation.

  1. When you add first button it does add it and compile first button.

    • Now #content has 1 button, which has 1 click event binded to it.
  2. When you add second button, it it getting added in DOM but it recompiling whole #content DOM, you know what it was already having 1 button with click event. When you #content DOM, it will recompile first directive again and will add click event again to it. Also it will click event to second button.

    • Now #content has 2 button
    • 1st button have 2 events bounded to it
    • 2nd button has 1 events bounded to it
  3. When you add 3rd button you will see below change

    • Now #content has 2 button
    • 1st button have 3 events bounded to it
    • 2nd button have 2 events bounded to it
    • 3rd button has 1 event bounded to it

I'd say that don't render controls on form by own by recompiling DOM every time. Suppose you have added are going to 100th control over the page, for that you are recompiling 99 control for no reason, technically which doesn't make sense. So better you should give responsibility of rendering controls to ng-repeat.

Markup

<div ng-controller="TestController">
    <button ng-click="addControl()">Add Button</button>
    <div ng-repeat="control in controls"><control count="{{control}}"></control></div>
    <div id="clicks"></div>
</div>

Controller

app.controller('TestController', function($scope, $compile) {
  $scope.count = 1;
  $scope.controls = [];
  $scope.controlsCount = 0;
  $scope.addControl = function() {
    $scope.controls.push(++$scope.controlsCount);
  };
});

Demo plunkr

share|improve this answer
    
Why my answer didn't get marked/upvoted? Any ideas/suggestions? – Medet Tleukabiluly Feb 17 at 7:11
    
@MedetTleukabiluly apology I didn't looked at it..let me look into it.. – Pankaj Parkar Feb 17 at 7:13
    
downvoter may I know reason behind downvote it? please do add comment so that I'll come to know whats wrong there, to improve me. – Pankaj Parkar Feb 18 at 9:36
    
@MedetTleukabiluly The issue was I was compiling #content every single time I created a new control (as pointed out by Pankaj). Even though unbinding works, I don't think that is the actual solution to my problem. – Aswin Ramakrishnan Feb 18 at 16:39
    
@AswinRamakrishnan but I don't understand why mine answer get downvote? do you have any idea :( – Pankaj Parkar Feb 18 at 16:40

You are binding click event twice, enter image description here Because directive is initialized twice in link phase, where click handler is binded, you triggered digest cycle by $compile(element.contents())(scope); which attached event to directive, because they are in link phase, but events can be binded multiple times, so directive has multiple click events, first suggestion is to unbind event first

element.unbind('click').bind('click');

You may ask how it comes that element may have multiple click events, here's how

//event1
document.body.onclick = function(){ console.log('event1'); }

//event2
var oldClick = document.body.onclick;
document.body.onclick = function(){ console.log('event2'); oldClick.bind(document.body).call(); }

//this will trigger
event2
event1

enter image description here

Working sample below

var clicks = 0;
var app = angular.module('test', []);

app.directive('control', function($compile) {
  var linker = function(scope, element, attrs) {
    element.html('<button style="background:grey;">Button ' + scope.count + '</button>');
    element.unbind('click').bind('click', function() {
      clicks++;
      $('#clicks').html('Clicked ' + clicks + ' times')
    });
    $compile(element.contents())(scope);
  };

  return {
    restrict: 'E',
    scope: {
      count: '@'
    },
    link: linker
  }
});

app.controller('TestController', function($scope, $compile) {
  $scope.count = 1;
  $scope.addControl = function() {
    $('#content').append('<control count="' + $scope.count+++'"></control>');
    $compile($('#content').contents())($scope);
  };
});
#content {
  margin-top: 10px;
}
#clicks {
  margin-top: 10px;
}
p {
  color: grey;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="TestController">
  <button ng-click="addControl()">Add Button</button>
  <div id="content"></div>
  <div id="clicks"></div>
</div>

<p>Add multiple buttons and click the button that was added in the beginning. Notice the Clicks to be increased multiple times.</p>
<p>For instance, add a bunch of buttons and click Button 1</p>

share|improve this answer
    
you have only element.unbind('click').bind('click'); this part is correct..but explanation is not upto the point.. could you look at mine answer..you will get better idea why its happening. Thanks :-) – Pankaj Parkar Feb 17 at 7:26
    
@PankajParkar i would love to see your edit tho :> – Medet Tleukabiluly Feb 17 at 7:28
    
I did edited mine answer, with little more detail..let me know if you don't understand anything. – Pankaj Parkar Feb 17 at 7:30
    
I was saying your edit to my answer would be awesome ;) – Medet Tleukabiluly Feb 17 at 7:31

You need think in angular way.

Just rewrite your link function like this

var linker = function(scope, element, attrs) {
 element.html('<button ng-click="onClick()"  style="background:grey;">Button ' + scope.count + ' Clicked {{clicks}} times</button>');

 scope.clicks=0;
 scope.onClick = function(){
  scope.clicks+;
 }
 $compile(element.contents())(scope);
};
share|improve this answer
    
I don't think this is the solution. I understood what I was doing wrong. Every time I added a control, I was compiling the contents of it's parent all over again. – Aswin Ramakrishnan Feb 17 at 7:06
    
I think, you need think in angular way. Using jQuery in angular code is bad practice. – Stepan Kasyanenko Feb 17 at 7:22

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.