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.
When you add first button it does add it and compile first button.
#content
has 1 button, which has 1 click event binded to it. 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.
#content
has 2 button When you add 3rd button you will see below change
#content
has 2 button 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);
};
});
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.