fra*_*llo 197 javascript model-view-controller mvvm angularjs angularjs-directive
我正在尝试构建一个指令,负责向声明它的元素添加更多指令.例如,我想构建一个负责添加的指令datepicker
,datepicker-language
和ng-required="true"
.
如果我尝试添加这些属性然后使用$compile
我显然会生成一个无限循环,所以我正在检查我是否已经添加了所需的属性:
angular.module('app')
.directive('superDirective', function ($compile, $injector) {
return {
restrict: 'A',
replace: true,
link: function compile(scope, element, attrs) {
if (element.attr('datepicker')) { // check
return;
}
element.attr('datepicker', 'someValue');
element.attr('datepicker-language', 'en');
// some more
$compile(element)(scope);
}
};
});
Run Code Online (Sandbox Code Playgroud)
当然,如果我没有$compile
元素,那么将设置属性但是指令不会被引导.
这种方法是正确的还是我做错了?有没有更好的方法来实现相同的行为?
UDPATE:鉴于这$compile
是实现这一目标的唯一方法,有没有办法跳过第一个编译传递(该元素可能包含几个子节点)?也许通过设置terminal:true
?
更新2:我已经尝试将指令放入一个select
元素中,正如预期的那样,编译运行两次,这意味着预期option
的数量是预期的两倍.
Kha*_* TO 259
如果您在单个DOM元素上有多个指令,并且它们的应用顺序很重要,则可以使用该priority
属性对其应用程序进行排序.更高的数字首先运行.如果未指定,则默认优先级为0.
编辑:讨论后,这是完整的工作解决方案.关键是删除属性:element.removeAttr("common-things");
,以及element.removeAttr("data-common-things");
(如果用户data-common-things
在html中指定)
angular.module('app')
.directive('commonThings', function ($compile) {
return {
restrict: 'A',
replace: false,
terminal: true, //this setting is important, see explanation below
priority: 1000, //this setting is important, see explanation below
compile: function compile(element, attrs) {
element.attr('tooltip', '{{dt()}}');
element.attr('tooltip-placement', 'bottom');
element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html
return {
pre: function preLink(scope, iElement, iAttrs, controller) { },
post: function postLink(scope, iElement, iAttrs, controller) {
$compile(iElement)(scope);
}
};
}
};
});
Run Code Online (Sandbox Code Playgroud)
工作插件可在以下网址获得:http://plnkr.co/edit/Q13bUt?p = preview
要么:
angular.module('app')
.directive('commonThings', function ($compile) {
return {
restrict: 'A',
replace: false,
terminal: true,
priority: 1000,
link: function link(scope,element, attrs) {
element.attr('tooltip', '{{dt()}}');
element.attr('tooltip-placement', 'bottom');
element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html
$compile(element)(scope);
}
};
});
Run Code Online (Sandbox Code Playgroud)
解释为什么我们必须设置terminal: true
和priority: 1000
(一个很高的数字):
当DOM准备就绪时,angular会遍历DOM以识别所有已注册的指令,并根据priority
这些指令是否在同一元素上逐个编译指令.我们设定的自定义指令的优先级为较高的数字,以确保它会被编译首先,用terminal: true
,其他指令将被跳过该指令被编译后.
编译我们的自定义指令时,它将通过添加指令并删除自身来修改元素,并使用$ compile服务来编译所有指令(包括那些被跳过的指令).
如果我们没有设置,terminal:true
并且priority: 1000
有可能在我们的自定义指令之前编译了一些指令.当我们的自定义指令使用$ compile来编译element =>再次编译已编译的指令时.这将导致不可预测的行为,特别是如果在我们的自定义指令之前编译的指令已经转换了DOM.
有关优先级和终端的更多信息,请查看如何理解指令的`terminal`?
同样修改模板的指令示例是ng-repeat
(priority = 1000),ng-repeat
编译时,ng-repeat
在应用其他指令之前复制模板元素.
感谢@ Izhaki的评论,这里是对ngRepeat
源代码的引用:https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js
小智 10
您只需使用简单的模板标记即可实现所有这些功能.有关示例,请参见http://jsfiddle.net/m4ve9/.请注意,我实际上不需要超级指令定义的编译或链接属性.
在编译过程中,Angular会在编译之前提取模板值,因此您可以在那里附加任何其他指令,Angular会为您处理它.
如果这是一个需要保留原始内部内容的超级指令,则可以使用transclude : true
并替换内部<ng-transclude></ng-transclude>
希望有所帮助,如果有什么不清楚,请告诉我
亚历克斯
这是一个解决方案,可以将需要动态添加的指令移动到视图中,还可以添加一些可选的(基本)条件逻辑.这使得指令保持干净,没有硬编码逻辑.
该指令采用一个对象数组,每个对象包含要添加的指令的名称和传递给它的值(如果有的话).
我一直在努力想到像这样的指令的用例,直到我认为添加一些仅根据某些条件添加指令的条件逻辑可能是有用的(尽管下面的答案仍然是设计的).我添加了一个可选if
属性,该属性应该包含一个bool值,表达式或函数(例如在控制器中定义),用于确定是否应该添加该指令.
我还使用attrs.$attr.dynamicDirectives
习惯来添加指令(例如,确切的属性声明中data-dynamic-directive
,dynamic-directive
没有硬编码字符串值检查).
angular.module('plunker', ['ui.bootstrap'])
.controller('DatepickerDemoCtrl', ['$scope',
function($scope) {
$scope.dt = function() {
return new Date();
};
$scope.selects = [1, 2, 3, 4];
$scope.el = 2;
// For use with our dynamic-directive
$scope.selectIsRequired = true;
$scope.addTooltip = function() {
return true;
};
}
])
.directive('dynamicDirectives', ['$compile',
function($compile) {
var addDirectiveToElement = function(scope, element, dir) {
var propName;
if (dir.if) {
propName = Object.keys(dir)[1];
var addDirective = scope.$eval(dir.if);
if (addDirective) {
element.attr(propName, dir[propName]);
}
} else { // No condition, just add directive
propName = Object.keys(dir)[0];
element.attr(propName, dir[propName]);
}
};
var linker = function(scope, element, attrs) {
var directives = scope.$eval(attrs.dynamicDirectives);
if (!directives || !angular.isArray(directives)) {
return $compile(element)(scope);
}
// Add all directives in the array
angular.forEach(directives, function(dir){
addDirectiveToElement(scope, element, dir);
});
// Remove attribute used to add this directive
element.removeAttr(attrs.$attr.dynamicDirectives);
// Compile element to run other directives
$compile(element)(scope);
};
return {
priority: 1001, // Run before other directives e.g. ng-repeat
terminal: true, // Stop other directives running
link: linker
};
}
]);
Run Code Online (Sandbox Code Playgroud)
<!doctype html>
<html ng-app="plunker">
<head>
<script src="//code.angularjs.org/1.2.20/angular.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
<script src="example.js"></script>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>
<body>
<div data-ng-controller="DatepickerDemoCtrl">
<select data-ng-options="s for s in selects" data-ng-model="el"
data-dynamic-directives="[
{ 'if' : 'selectIsRequired', 'ng-required' : '{{selectIsRequired}}' },
{ 'tooltip-placement' : 'bottom' },
{ 'if' : 'addTooltip()', 'tooltip' : '{{ dt() }}' }
]">
<option value=""></option>
</select>
</div>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
104857 次 |
最近记录: |