简体   繁体   中英

AngularJS: Dynamically binding Click in directive gets fired multiple times

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.

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

You are binding click event twice, 在此处输入图片说明 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

在此处输入图片说明

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> 

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM