Динамическое добавление ng-click в функцию директивной ссылки
Я пытаюсь создать директиву, которая позволила бы определить элемент как интерактивный или нет, и будет определяться следующим образом:
<page is-clickable="true">
transcluded elements...
</page>
Я хочу, чтобы получившийся HTML был:
<page is-clickable="true" ng-click="onHandleClick()">
transcluded elements...
</page>
Моя реализация директивы выглядит следующим образом:
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');
};
}
};
});
Я вижу, что после добавления нового атрибута Angular не знает о ng-click
, поэтому он не запускается. Я попытался добавить $compile
после того, как атрибут установлен, но он вызывает бесконечный цикл link/compile.
Я знаю, что могу просто проверить внутри onHandleClick()
функцию, если значение isClickable
равно true
, но мне любопытно, как можно было бы это сделать, динамически добавляя событие ng-click
, потому что мне может понадобиться сделать это с помощью нескольких других директив ng-*
, и я не хочу добавлять лишние накладные расходы. Любые идеи?
Ответы
Ответ 1
Лучшее решение (новое):
Прочитав Angular docs, я наткнулся на это:
Вы можете указать шаблон как строку, представляющую шаблон, или как функция, которая принимает два аргумента tElement и tAttrs (описанные в функция api компиляции ниже) и возвращает строковое значение, представляющее шаблон.
Итак, моя новая директива выглядит следующим образом: (Я считаю, что это подходящий способ "Angular" для этого типа вещей)
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');
};
}
};
});
Обратите внимание на новую функцию шаблона. Теперь я манипулирую шаблоном внутри этой функции перед ее компиляцией.
Альтернативное решение (старое):
Добавлен replace: true
, чтобы избавиться от проблемы бесконечного цикла при перекомпиляции директивы. И затем в функции связи я просто перекомпилирую элемент после добавления нового атрибута. Одна вещь, которую нужно отметить, потому что у меня была директива ng-transclude
для моего элемента, мне нужно было удалить ее, чтобы она не пыталась перекрыть что-либо во втором компиляторе, потому что ничего не нужно переводить.
Вот как выглядит моя директива:
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');
};
}
};
});
Я не думаю, что повторная компиляция шаблона во второй раз идеальна, поэтому я чувствую, что все еще есть способ сделать это, прежде чем шаблон будет скомпилирован в первый раз.
Ответ 2
Вы всегда можете просто изменить свой ng-click, чтобы выглядеть так:
ng-click="isClickable && someFunction()"
Никакой пользовательской директивы не требуется:)
Вот пример JSFiddle: http://jsfiddle.net/robianmcd/5D4VR/
Ответ 3
Обновленный ответ
"Путь Angular" вообще не будет ручным манипулятором DOM. Итак, нам нужно избавиться от добавления и удаления атрибутов.
Измените шаблон на:
template: '<div ng-click="onHandleClick()" ng-transclude></div>'
И в директиве проверьте атрибут isClickable
, чтобы решить, что делать при нажатии:
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');
};
}
Вы также можете поместить атрибут isClickable в область директивы, чтобы он мог динамически изменять свое поведение.
Старый ответ (неверный)
link
запускается после компиляции шаблона. Используйте controller
для изменения шаблона перед компиляцией:
app.directive('page', function() {
return {
restrict: 'E',
template: '<div ng-transclude></div>',
transclude: true,
controller: function(scope, element, attrs) {
// your code
}
};
});
Ответ 4
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){
}
};
});
кредит Erstad.Stephen и Илан Фрумер
BTW с ограничением: "E" браузер разбился: (
Ответ 5
Это моя версия решения @DiscGolfer, где я также добавил поддержку атрибутов.
.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;
}
}
};
Предоставляется более общий и полный выбор http://plnkr.co/edit/4PcMnpq59ebZr2VrOI07?p=preview
Единственная проблема с этим решением заключается в том, что replace
устарел в AngularJS.
Ответ 6
Я думаю, что это должно быть лучше:
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');
};
}
};
});
Ответ 7
module.factory("ibDirectiveHelpers", ["ngClickDirective", function (ngClick) {
return {
click: function (scope, element, fn) {
var attr = {ngClick: fn};
ngClick[0].compile(element, attr)(scope, element, attr);
}
};
}]);
использование:
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
}]