Reputation: 4313
I've been working on this issue for two days now. I get the feeling it should be a lot simpler.
I'd like to create a directive that is used as follows:
<my-directive ng-something="something">
content
</my-directive>
and has as output:
<my-directive ng-something="something" ng-more="more">
content
</my-directive>
Naturally it would have a linking function and controller that do some work, but the main concerns are:
For example, say I want to create an element that does something internally when it is clicked on:
<click-count ng-repeat="X in ['A', 'B', 'C']"> {{ X }} </click-count>
which could compile into something like this:
<click-count ng-click="internalFn()"> A </click-count>
<click-count ng-click="internalFn()"> B </click-count>
<click-count ng-click="internalFn()"> C </click-count>
where internalFn
would be defined in the internal scope of the clickCount
directive.
One of my attempts in is this Plunker: http://plnkr.co/edit/j9sUUS?p=preview
Since Plunker seems to be down, here's the code:
angular.module('app', []).directive('clickCount', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
ccModel: '='
},
compile: function(dElement) {
dElement.attr("ngClick", "ccModel = ccModel + 1");
return function postLink($scope, iElement, iAttrs, controller, transclude) {
transclude(function(cloned) { iElement.append(cloned); });
};
},
controller: function ($scope) {
$scope.ccModel = 0;
}
};
});
This is some HTML using the directive:
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app">
<hr> The internal 'ng-click' doesn't work:
<click-count ng-repeat="X in ['A', 'B', 'C']" cc-model="counter">
{{ X }}, {{ counter }}
</click-count>
<hr> But an external 'ng-click' does work:
<click-count ng-repeat="X in ['A', 'B', 'C']" cc-model="bla" ng-init="counter = 0" ng-click="counter = counter + 1">
{{ X }}, {{ counter }}
</click-count>
<hr>
</body>
</html>
And because the element keeps its name, css can be used as follows:
click-count {
display: block;
border: solid 1px;
background-color: lightgreen;
font-weight: bold;
margin: 5px;
padding: 5px;
}
I have several ideas on what might be wrong with it, but I've tried a number of alternative approaches. If I mess around in the linker, perhaps try to $compile
again, the controller function has to be called twice too. Anyway, an example of how to do it properly would be most appreciated.
Upvotes: 4
Views: 743
Reputation: 8789
As fas as I understand, you are trying to modify DOM element and add some directives using attributes. That means that your directive should run before all other directives run. In order to control directives execution order Angular provides priority
property. Most of directives runs on priority 0
, that means that if your directive have larger priority it will run before. But unfortunately ngRepeat
has not only priority 1000
, but also defined with terminal:true
, which means that once element has ngRepeat
you can't specify on the same element directive with higher priority. You can add attributes and behavior, but not directives that should run before ngRepeat
. However there is a workaround for ngClick
:
angular.module('app', []).directive('clickCount', function() {
return {
restrict: 'E',
replace: true,
compile: function(tElement) {
return {
pre: function(scope, iElement) {
iElement.attr('ng-click', 'counter = counter +1'); // <- Add attribute
},
post: function(scope, iElement) {
iElement.on('click', function() { // <- Add behavior
scope.$apply(function(){ // <- Since scope variables may be modified, don't forget to apply the scope changes
scope.$eval(iElement.attr('ng-click')); // <- Evaluate expression defined in ng-click attribute in context of scope
});
});
}
}
}
};
});
JSBin: http://jsbin.com/sehobavo/1/edit
Another workaround is to recompile your directive without ngRepeat
:
angular.module('app', []).directive('clickCount', function($compile) {
return {
restrict: 'E',
replace: true,
compile: function(tElement) {
return {
pre: function(scope, iElement) {
if(iElement.attr('ng-repeat')) { // <- Avoid recursion
iElement.attr('ng-click', 'counter = counter +1'); // <- Add custom attributes and directives
iElement.removeAttr('ng-repeat'); // <- Avoid recursion
$compile(iElement)(scope); // <- Recompile your element to make other directives work
}
}
}
}
};
});
JSBin: http://jsbin.com/hucunuqu/4/edit
Upvotes: 1