1

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.

3 Answers 3

2

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

Sign up to request clarification or add additional context in comments.

4 Comments

Why my answer didn't get marked/upvoted? Any ideas/suggestions?
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.
@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.
@AswinRamakrishnan but I don't understand why mine answer get downvote? do you have any idea :(
1

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>

3 Comments

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 :-)
I did edited mine answer, with little more detail..let me know if you don't understand anything.
I was saying your edit to my answer would be awesome ;)
-1

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

2 Comments

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.
I think, you need think in angular way. Using jQuery in angular code is bad practice.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.