Ajouter ng-click dynamiquement dans la fonction de liaison directive

J'essaie de créer une directive qui permettrait de définir un élément 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> 

L'implémentation de ma 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 que, après avoir ajouté le nouvel attribut, Angular ne connaît pas le ng-click , donc il ne lance pas. J'ai essayé d'ajouter une $compile après l'attribution de l'attribut, mais cela provoque une boucle de compilation / compilation infinie.

Je sais que je peux vérifier à l'intérieur de la fonction onHandleClick() si la valeur isClickable est true , mais je suis curieux de savoir comment cela se fera avec l'ajout dynamique d'un événement ng-click , car je pourrais avoir besoin de cela avec plusieurs autres ng-* directives et je ne veux pas ajouter des frais généraux inutiles. Des idées?

Meilleure solution (nouvelle):

Après avoir lu les documents angulaires, je suis tombé sur ce sujet:

Vous pouvez spécifier le modèle comme une chaîne représentant le modèle ou comme une fonction qui prend deux arguments tElement et tAttrs (décrit dans la fonction de compilation api 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 le mode approprié "Angulaire" 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'); }; } }; }); 

Notez la nouvelle fonction de modèle. Maintenant, je manipule le modèle dans cette fonction avant de le compiler.

Solution alternative (ancienne):

Ajout ajouté 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 liaison, je viens de recompiler l'élément après avoir ajouté le nouvel attribut. Une chose à noter cependant, parce que j'avais une directive ng-transclude sur mon élément, je devais supprimer cela afin qu'il n'essaie pas de transporter quelque chose sur la deuxième compilation, car il n'y a rien à transmettre.

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 la recompilation du modèle une deuxième fois soit idéale, mais je pense qu'il existe encore un moyen de le faire avant que le modèle ne soit compilé pour la première fois.

Vous pouvez toujours 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/

Réponse mise à jour

"The Angular Way" ne serait pas une manipulation DOM manuelle. Nous devons donc éliminer l'ajout et la suppression des attributs.

DEMO

Modifiez le modèle à:

 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:

  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 mettre l'attribut isClickable dans la portée de la directive afin de pouvoir modifier son comportement de façon dynamique.

Ancienne réponse (mauvaise)

link est exécuté après la compilation du modèle. Utilisez le controller pour modifier 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 restriction: 'E' le navigateur s'est écrasé 🙁

C'est ma version de la solution @DiscGolfer où j'ai ajouté un support pour les 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 le replace est obsolète dans AngularJS.

Je pense qu'il devrait être mieux comme 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) { angular.element(element).on('click', scope.onHandleClick); } scope.onHandleClick = function() { console.log('onHandleClick'); }; } }; }); 
 module.factory("ibDirectiveHelpers", ["ngClickDirective", function (ngClick) { return { click: function (scope, element, fn) { var attr = {ngClick: fn}; ngClick[0].compile(element, attr)(scope, element, attr); } }; }]); 

utilisation:

 module.controller("demoController",["$scope","$element","ibDirectiveHelpers",function($scope,$element,ibDirectiveHelpers){ $scope.demoMethod=function(){console.log("demoMethod");}; ibDirectiveHelpers.click($scope,$element,"demoMethod()");//uses html notation //or ibDirectiveHelpers.click($scope,$element,function(){$scope.demoMethod();});//uses inline notation }]