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

Javascript AngularJS:如何创建一个transcluding元素指令来保留属性指令并可以添加新的指令?,javascript,angularjs,angularjs-directive,transclusion,Javascript,Angularjs,Angularjs Directive,Transclusion,这个问题我已经研究了两天了。我觉得应该简单得多 问题描述 我想创建一个指令,如下所示: <my-directive ng-something="something"> content </my-directive> click-count { display: block; border: solid 1px; background-color: lightgreen; font-weight: bold; margin: 5px; pa

这个问题我已经研究了两天了。我觉得应该简单得多

问题描述 我想创建一个指令,如下所示:

<my-directive ng-something="something">
    content
</my-directive>
click-count {
  display: block;
  border: solid 1px;
  background-color: lightgreen;
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
这是一些使用指令的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>

我对它可能有什么问题有一些想法,但我尝试了许多替代方法。如果我在链接器中乱搞,可能再次尝试
$compile
,控制器函数也必须调用两次。无论如何,最好举一个如何正确执行的例子。

据我所知,您正在尝试修改DOM元素并使用属性添加一些指令。这意味着您的指令应该在所有其他指令运行之前运行。为了控制指令的执行顺序,Angular提供了
优先级
属性。大多数指令在优先级
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
            });
          });
        }
      }
    }
  };
});

JSBin:

Hi Vadim。谢谢你的解决方案。然而,我担心这对于这个示例来说太具体了,而我想找到一个通用的解决方案,例如,您正在复制
ng单击
,而不是使用它。此外,您还更改了指令的外部接口:计数器是外部作用域的字段,可能对指令的用户隐藏了一个字段。这种逻辑应该局限于内部范围让我们一步一个脚印:如果我们假设不使用
terminal:true
指令,那么问题可以解决吗?@mhelvens我添加了更通用的解决方案,使用指令,但不复制它们。它只能与
ngRepeat
一起使用,但也可以轻松扩展以支持单个案例。祝您好运,谢谢您提出的有趣问题。第二种解决方案更好,因为它实际上使用了
ng click
。我自己也试过类似的方法。问题是控制器功能(如果有)也运行了两次(包括任何副作用)。它仍然将内部状态暴露于外部。-尽管如此,我还是从你的代码中学到了一些有用的东西。例如,您没有使用
transclude
:-)非常整洁。我只是觉得保持元素的内容总是有必要的。
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;
    }
  };
});
<!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>
click-count {
  display: block;
  border: solid 1px;
  background-color: lightgreen;
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
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
            });
          });
        }
      }
    }
  };
});
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
          }
        }
      }
    }
  };
});