Aggiungi ng-clic in modo dinamico nella funzione di collegamento della direttiva

Sto provando a creare una direttiva che consenta a un elemento di essere definito come selezionabile o meno, e sarebbe definito in questo modo:

 transcluded elements...  

Voglio che l’HTML risultante sia:

  transcluded elements...  

La mia implementazione direttiva assomiglia a questa:

 app.directive('page', function() { return { restrict: 'E', template: '
', 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'); }; } }; });

Vedo che dopo aver aggiunto il nuovo attributo, Angular non conosce il ng-click , quindi non sta sparando. Ho provato ad aggiungere una $compile dopo che l’attributo è stato impostato, ma causa un loop infinito di collegamento / compilazione.

So che posso semplicemente controllare all’interno della funzione onHandleClick() se il valore isClickable è true , ma sono curioso di sapere come si farebbe con l’aggiunta dynamic di un evento ng-click perché potrei aver bisogno di farlo con molti altri ng-* direttive e non voglio aggiungere spese inutili. Qualche idea?

Soluzione migliore (nuova):

Dopo aver letto i documenti Angular mi sono imbattuto in questo:

È ansible specificare il modello come stringa che rappresenta il modello o come funzione che accetta due argomenti tElement e tAttrs (descritti nella funzione di compilazione api sotto) e restituisce un valore di stringa che rappresenta il modello.

Quindi la mia nuova direttiva assomiglia a questo: (Credo che questo sia il modo “Angolare” appropriato per questo tipo di cose)

 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 '
'; }, transclude: true, link: function(scope, element, attrs) { scope.onHandleClick = function() { console.log('onHandleClick'); }; } }; });

Notare la nuova funzione del modello. Ora sto manipolando il template all’interno di quella funzione prima che sia compilato.

Soluzione alternativa (vecchia):

Aggiunto replace: true per eliminare il problema del ciclo infinito durante la ricompilazione della direttiva. E poi nella funzione di collegamento ricompongo l’elemento dopo aver aggiunto il nuovo attributo. Una cosa da notare però, perché avevo una direttiva ng-transclude sul mio elemento, avevo bisogno di rimuoverlo in modo che non provasse a escludere nulla sulla seconda compilazione, perché non c’è nulla da escludere.

Ecco come appare ora la mia direttiva:

 app.directive('page', function() { return { restrict: 'E', replace: true, template: '
', 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'); }; } }; });

Non penso che ricompilare il template una seconda volta sia l’ideale, quindi ritengo che ci sia ancora un modo per farlo prima che il template sia compilato per la prima volta.

Potresti sempre modificare il tuo ng-click per assomigliare a questo:

ng-click="isClickable && someFunction()"

Nessuna direttiva personalizzata richiesta 🙂

Ecco un JSFiddle che lo demo: http://jsfiddle.net/robianmcd/5D4VR/

Risposta aggiornata

“The Angular Way” non sarebbe affatto una manipolazione manuale del DOM. Quindi, dobbiamo sbarazzarci di aggiungere e rimuovere attributi.

DEMO

Cambia il modello in:

 template: '
'

E nella direttiva controlla l’attributo isClickable per decidere cosa fare quando si fa clic:

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

È inoltre ansible inserire l’attributo isClickable nell’ambito della direttiva in modo che possa modificarne il comportamento in modo dinamico.

Vecchia risposta (errata)

link viene eseguito dopo che il modello è stato compilato. Usa controller per le modifiche sul modello prima della compilazione:

 app.directive('page', function() { return { restrict: 'E', template: '
', transclude: true, controller: function(scope, element, attrs) { // your code } }; });

HTML

 
hhhh

JS

 app.directive('page', function($compile) { return { priority:1001, // compiles first terminal:true, // prevent lower priority directives to compile after it template: '
', 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){ } }; });

credito a Erstad.Stephen e Ilan Frumer

BTW con restrizioni: ‘E’ il browser si è bloccato 🙁

Questa è la mia versione della soluzione @DiscGolfer in cui ho aggiunto anche il supporto per gli attributi.

 .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 esempio più generico e completo è fornito http://plnkr.co/edit/4PcMnpq59ebZr2VrOI07?p=preview

L’unico problema con questa soluzione è che la replace è deprecata in AngularJS.

Penso che dovrebbe essere meglio così:

 app.directive('page', function() { return { restrict: 'E', template: '
', 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); } }; }]); 

uso:

 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 }]