我正在尝试创建一个指令,允许将元素定义为可点击或不可点击,并且将定义如下:
<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
,但它会导致无限链接/编译循环。
我知道如果onHandleClick()
值为isClickable
,我可以在true
函数内部进行检查,但我很好奇如何动态添加{ng-click
1}}事件因为我可能需要使用多个其他ng-*
指令来执行此操作,并且我不想添加不必要的开销。有什么想法吗?
答案 0 :(得分:28)
阅读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');
};
}
};
});
我不认为第二次重新编译模板是理想的,所以我觉得在第一次编译模板之前还有一种方法可以做到这一点。
答案 1 :(得分:15)
您可以随时修改ng-click,如下所示:
ng-click="isClickable && someFunction()"
不需要自定义指令:)
这是一个JSFiddle演示它:http://jsfiddle.net/robianmcd/5D4VR/
答案 2 :(得分:3)
“Angular Way”根本不是手动DOM操作。因此,我们需要摆脱添加和删除属性。
DEMO
将模板更改为:
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
}
};
});
答案 3 :(得分:2)
<强> 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){
}
};
});
BTW with restrict:'E'浏览器崩溃:(
答案 4 :(得分:1)
这是我的@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
此解决方案的唯一问题是AngularJS中不推荐使用replace
。
答案 5 :(得分:0)
我认为它应该更好:
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');
};
}
};
});
答案 6 :(得分:-1)
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
}]