J'essaie de créer une directive qui permettrait à un élément d'être défini comme cliquable ou non, et serait défini comme ceci:
_<page is-clickable="true">
transcluded elements...
</page>
_
Je veux que le HTML résultant soit:
_<page is-clickable="true" ng-click="onHandleClick()">
transcluded elements...
</page>
_
Mon implémentation de directive ressemble à ceci:
_app.directive('page', function() {
return {
restrict: 'E',
template: '<div ng-transclude></div>',
transclude: true,
link: function(scope, element, attrs) {
var isClickable = angular.isDefined(attrs.isClickable) && scope.$eval(attrs.isClickable) === true ? true : false;
if (isClickable) {
attrs.$set('ngClick', 'onHandleClick()');
}
scope.onHandleClick = function() {
console.log('onHandleClick');
};
}
};
});
_
Je peux voir qu'après avoir ajouté le nouvel attribut, Angular ne connaît pas le _ng-click
_, donc il ne se déclenche pas. J'ai essayé d'ajouter un _$compile
_ après que l'attribut soit défini, mais il provoque une boucle de liaison/compilation infinie.
Je sais que je peux simplement vérifier à l'intérieur de la fonction onHandleClick()
si la valeur isClickable
est true
, mais je suis curieux de savoir comment procéder en ajoutant dynamiquement un _ng-click
_ événement car je dois peut-être faire cela avec plusieurs autres directives _ng-*
_ et je ne veux pas ajouter de surcharge inutile. Des idées?
Après avoir lu les Angular docs je suis tombé sur ceci:
Vous pouvez spécifier le modèle en tant que chaîne représentant le modèle ou en tant que fonction qui prend deux arguments tElement et tAttrs (décrits dans l'api de la fonction de compilation ci-dessous) et renvoie une valeur de chaîne représentant le modèle.
Donc ma nouvelle directive ressemble à ceci: (je crois que c'est la façon "angulaire" appropriée de procéder pour ce genre de chose)
app.directive('page', function() {
return {
restrict: 'E',
replace: true,
template: function(tElement, tAttrs) {
var isClickable = angular.isDefined(tAttrs.isClickable) && eval(tAttrs.isClickable) === true ? true : false;
var clickAttr = isClickable ? 'ng-click="onHandleClick()"' : '';
return '<div ' + clickAttr + ' ng-transclude></div>';
},
transclude: true,
link: function(scope, element, attrs) {
scope.onHandleClick = function() {
console.log('onHandleClick');
};
}
};
});
Remarquez la nouvelle fonction de modèle. Maintenant, je manipule le modèle à l'intérieur de cette fonction avant qu'il ne soit compilé.
Ajoutée replace: true
pour se débarrasser du problème de boucle infinie lors de la recompilation de la directive. Et puis dans la fonction de lien, je recompile simplement l'élément après avoir ajouté le nouvel attribut. Une chose à noter cependant, car j'avais un ng-transclude
directive sur mon élément, j'avais besoin de la supprimer pour ne pas essayer de transclure quoi que ce soit sur la deuxième compilation, car il n'y a rien à transclure.
Voici à quoi ressemble ma directive maintenant:
app.directive('page', function() {
return {
restrict: 'E',
replace: true,
template: '<div ng-transclude></div>',
transclude: true,
link: function(scope, element, attrs) {
var isClickable = angular.isDefined(attrs.isClickable) && scope.$eval(attrs.isClickable) === true ? true : false;
if (isClickable) {
attrs.$set('ngClick', 'onHandleClick()');
element.removeAttr('ng-transclude');
$compile(element)(scope);
}
scope.onHandleClick = function() {
console.log('onHandleClick');
};
}
};
});
Je ne pense pas que recompiler le modèle une deuxième fois soit idéal, donc je pense qu'il y a encore un moyen de le faire avant que le modèle ne soit compilé la première fois.
Vous pouvez toujours simplement modifier votre ng-click pour ressembler à ceci:
ng-click="isClickable && someFunction()"
Aucune directive personnalisée requise :)
Voici une démonstration de JSFiddle: http://jsfiddle.net/robianmcd/5D4VR/
"La Angular Way" ne serait pas du tout une manipulation manuelle du DOM. Donc, nous devons nous débarrasser de l'ajout et de la suppression d'attributs.
DEMO
Modifiez le modèle en:
template: '<div ng-click="onHandleClick()" ng-transclude></div>'
Et dans la directive, vérifiez l'attribut isClickable
pour décider quoi faire lorsque vous cliquez dessus:
link: function(scope, element, attrs) {
var isClickable = angular.isDefined(attrs.isClickable) && scope.$eval(attrs.isClickable) === true ? true : false;
scope.onHandleClick = function() {
if (!isClickable) return;
console.log('onHandleClick');
};
}
Vous pouvez également placer l'attribut isClickable dans la portée de la directive afin qu'il puisse changer dynamiquement son comportement.
link
est exécuté après la compilation du modèle. Utilisez controller
pour les modifications sur le modèle avant de compiler:
app.directive('page', function() {
return {
restrict: 'E',
template: '<div ng-transclude></div>',
transclude: true,
controller: function(scope, element, attrs) {
// your code
}
};
});
[~ # ~] html [~ # ~]
<div page is-clickable="true">hhhh</div>
[~ # ~] js [~ # ~]
app.directive('page', function($compile) {
return {
priority:1001, // compiles first
terminal:true, // prevent lower priority directives to compile after it
template: '<div ng-transclude></div>',
transclude: true,
compile: function(el,attr,transclude) {
el.removeAttr('page'); // necessary to avoid infinite compile loop
var contents = el.contents().remove();
var compiledContents;
return function(scope){
var isClickable = angular.isDefined(attr.isClickable)?scope.$eval(attr.isClickable):false;
if(isClickable){
el.attr('ng-click','onHandleClick()');
var fn = $compile(el);
fn(scope);
scope.onHandleClick = function() {
console.log('onHandleClick');
};
}
if(!compiledContents) {
compiledContents = $compile(contents, transclude);
}
compiledContents(scope, function(clone, scope) {
el.append(clone);
});
};
},
link:function(scope){
}
};
});
crédit à Erstad.Stephen et Ilan Frumer
BTW avec restrict: 'E' le navigateur est tombé en panne :(
Ceci est ma version de la solution @DiscGolfer où j'ai également ajouté la prise en charge des attributs.
.directive("page", function() {
return {
transclude: true,
replace: true,
template: function(tElement, tAttr) {
var isClickable = angular.isDefined(tAttrs.isClickable) && eval(tAttrs.isClickable) === true ? true : false;
if (isClickable) {
tElement.attr("ng-click", "onHandleClick()");
}
tElement.attr("ng-transclude", "");
if (tAttr.$attr.page === undefined) {
return "<" + tElement[0].outerHTML.replace(/(^<\w+|\w+>$)/g, 'div') + ">";
} else {
tElement.removeAttr(tAttr.$attr.page);
return tElement[0].outerHTML;
}
}
};
Un échantillon plus générique et complet est fourni http://plnkr.co/edit/4PcMnpq59ebZr2VrOI07?p=preview
Le seul problème avec cette solution est que replace
est déconseillé dans AngularJS.
Je pense que ça devrait être mieux comme ça:
app.directive('page', function() {
return {
restrict: 'E',
template: '<div ng-transclude></div>',
transclude: true,
link: function(scope, element, attrs) {
var isClickable = angular.isDefined(attrs.isClickable) && scope.$eval(attrs.isClickable) === true ? true : false;
if (isClickable) {
angular.element(element).on('click', scope.onHandleClick);
}
scope.onHandleClick = function() {
console.log('onHandleClick');
};
}
};
});