AngularJS:如何创建一个保留属性指令并可以添加新指令的transcluding元素指令?

时间:2014-02-20 15:58:53

标签: javascript angularjs angularjs-directive transclusion

我已经在这个问题上工作了两天了。我觉得它应该更简单。

问题描述

我想创建一个使用如下的指令:

<my-directive ng-something="something">
    content
</my-directive>

并输出:

<my-directive ng-something="something" ng-more="more">
    content
</my-directive>

当然它会有一个链接功能和控制器做一些工作,但主要关注点是:

  • DOM元素保留原始名称,以便可以应用直观的CSS样式,
  • 已经存在的属性指令保持正常工作,
  • 可以通过element指令本身添加新的属性指令。

实施例

例如,假设我想创建一个在内部执行某些操作的元素:

<click-count ng-repeat="X in ['A', 'B', 'C']"> {{ X }} </click-count>

可以编译成这样的东西:

<click-count ng-click="internalFn()"> A </click-count>
<click-count ng-click="internalFn()"> B </click-count>
<click-count ng-click="internalFn()"> C </click-count>

其中internalFn将在clickCount指令的内部范围中定义。

尝试

我的一次尝试就是这个Plunker:http://plnkr.co/edit/j9sUUS?p=preview

由于Plunker似乎失败了,这是代码:

angular.module('app', []).directive('clickCount', function() {
  return {
    restrict: 'E',
    replace: true,
    transclude: true,
    scope: {
      ccModel: '='
    },
    compile: function(dElement) {
      dElement.attr("ngClick", "ccModel = ccModel + 1");

      return function postLink($scope, iElement, iAttrs, controller, transclude) {
        transclude(function(cloned) { iElement.append(cloned); });
      };
    },
    controller: function ($scope) {
        $scope.ccModel = 0;
    }
  };
});

这是使用指令的一些HTML:

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>
<body ng-app="app">
  <hr> The internal 'ng-click' doesn't work:
  <click-count ng-repeat="X in ['A', 'B', 'C']" cc-model="counter">
    {{ X }}, {{ counter }}
  </click-count>
  <hr> But an external 'ng-click' does work:
  <click-count ng-repeat="X in ['A', 'B', 'C']" cc-model="bla" ng-init="counter = 0" ng-click="counter = counter + 1">
    {{ X }}, {{ counter }}
  </click-count>
  <hr>
</body>
</html>

因为元素保持其名称,所以可以按如下方式使用css:

click-count {
  display: block;
  border: solid 1px;
  background-color: lightgreen;
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}

我对它可能出现的问题有几点看法,但我尝试了许多替代方法。如果我在链接器中乱七八糟,也许再次尝试$compile,控制器函数也必须被调用两次。无论如何,我们非常感谢如何正确地做到这一点。

1 个答案:

答案 0 :(得分:1)

正如我所理解的那样,您正在尝试修改DOM元素并使用属性添加一些指令。这意味着您的指令应该在所有其他指令运行之前运行。为了控制指令,执行顺序Angular提供priority属性。大多数指令都以优先级0运行,这意味着如果您的指令具有更高的优先级,它将在之前运行。但遗憾的是,ngRepeat不仅具有优先级1000,还具有terminal:true定义,这意味着一旦元素具有ngRepeat,您就无法在相同的元素指令上指定更高优先级ngRepeat优先。您可以添加属性和行为,但不能添加应在ngClick之前运行的指令。但是angular.module('app', []).directive('clickCount', function() { return { restrict: 'E', replace: true, compile: function(tElement) { return { pre: function(scope, iElement) { iElement.attr('ng-click', 'counter = counter +1'); // <- Add attribute }, post: function(scope, iElement) { iElement.on('click', function() { // <- Add behavior scope.$apply(function(){ // <- Since scope variables may be modified, don't forget to apply the scope changes scope.$eval(iElement.attr('ng-click')); // <- Evaluate expression defined in ng-click attribute in context of scope }); }); } } } }; }); 有一种解决方法:

ngRepeat

JSBin:http://jsbin.com/sehobavo/1/edit

另一种解决方法是在没有angular.module('app', []).directive('clickCount', function($compile) { return { restrict: 'E', replace: true, compile: function(tElement) { return { pre: function(scope, iElement) { if(iElement.attr('ng-repeat')) { // <- Avoid recursion iElement.attr('ng-click', 'counter = counter +1'); // <- Add custom attributes and directives iElement.removeAttr('ng-repeat'); // <- Avoid recursion $compile(iElement)(scope); // <- Recompile your element to make other directives work } } } } }; }); 的情况下重新编译您的指令:

{{1}}

JSBin:http://jsbin.com/hucunuqu/4/edit