AngularJS:嵌入指令模板

9
如何在下面的情况下使用转译。目的是在html(部分)文件中使用标记,而不是在指令模板中定义它。
我在这里找到了一个很棒的树形指令。(来源:source) 原始链接:http://jsfiddle.net/n8dPm/ 我尝试使用转换内容来代替在指令中定义模板。我还将Angular更新到1.2.0.rc2。 更新后的链接:http://jsfiddle.net/aZx7B/2/ 出现以下错误:
TypeError: Object [object Object] has no method '$transclude'
代码:
module.directive("tree", function($compile) {
    return {
        restrict: "E",
        transclude: true,
        scope: {family: '='},
        template:       
            '<ul>' + 
                '<li ng-transclude></li>' +
                '<li ng-repeat="child in family.children">' +
                    '<tree family="child"></tree>' +
                '</li>' +
            '</ul>',
        compile: function(tElement, tAttr) {
            var contents = tElement.contents().remove();
            var compiledContents;
            return function(scope, iElement, iAttr) {
                if(!compiledContents) {
                    compiledContents = $compile(contents);
                }
                compiledContents(scope, function(clone, scope) {
                         iElement.append(clone); 
                });
            };
        }
    };
});

<div ng-app="myapp">
    <div ng-controller="TreeCtrl">
        <tree family="family">
            <p>{{ family.name }}</p>
        </tree>
    </div>
</div>

编辑:

在David的建议下,做了一些更改。 http://jsfiddle.net/aZx7B/3/ 现在它输出“Parent”。但是更改familytreeFamily并没有起作用。


1
这里有几个问题:你在转移中引用了family.name,但是family是指令作用域的一部分,不会可用。你需要使用treeFamily.name。另外,你嵌套的树木不会有转移内容。如果你使用提供给编译函数的转移函数(第三个参数)而不是ngTransclude,你可能会更进一步。 - David Bennett
谢谢David,根据一些变化进行更新。 - bsr
我刚刚做了类似的事情,想要将我的HTML保留在模板中。但是递归无法正常工作(我认为是无限摘要),除非我在链接函数内手动编译它。我真的很想知道为什么会这样,这样我就可以根据知识而不是“因为它就是这样”来做出编码决策。 - Stevo
我的修改后的答案能否解释为什么模板无法正确输出?通过我上一个代码版本,您应该能够将任何您想要的内容传递到您的自定义指令中,包括其他具有自己模板的自定义指令。 - Erstad.Stephen
3个回答

8
你需要在模板中输出该家族的名称: http://jsfiddle.net/roadprophet/DsvX6/
module.directive("tree", function($compile) {
    return {
        restrict: "E",
        transclude: true,
        scope: {family: '='},
        template:       
            '<ul>' + 
                '<li ng-transclude></li>' +
                '<li ng-repeat="child in family.children">' +
                    '<tree family="child">{{family.name}}</tree>' +
                '</li>' +
            '</ul>',
        compile: function(tElement, tAttr, transclude) {
            var contents = tElement.contents().remove();
            var compiledContents;
            return function(scope, iElement, iAttr) {
                if(!compiledContents) {
                    compiledContents = $compile(contents, transclude);
                }
                compiledContents(scope, function(clone, scope) {
                         iElement.append(clone); 
                });
            };
        }
    };
});

编辑

你也可以通过这种方式简化操作:http://jsfiddle.net/roadprophet/DsvX6/2/

<div ng-app="myapp">
    <div ng-controller="TreeCtrl">
        <tree family="treeFamily">           
        </tree>
    </div>
</div>


module.directive("tree", function($compile) {
    return {
        restrict: "E",
        transclude: true,
        scope: {family: '='},
        template:       
            '<ul>' + 
                '<li ng-transclude></li>' +
                '<p>{{ family.name }}</p>' + 
                '<li ng-repeat="child in family.children">' +
                    '<tree family="child"></tree>' +
                '</li>' +
            '</ul>',
        compile: function(tElement, tAttr, transclude) {
            var contents = tElement.contents().remove();
            var compiledContents;
            return function(scope, iElement, iAttr) {
                if(!compiledContents) {
                    compiledContents = $compile(contents, transclude);
                }
                compiledContents(scope, function(clone, scope) {
                         iElement.append(clone); 
                });
            };
        }
    };
});

编辑 问题的根源是相同的。没有将模板传递给内部的树形指令。 http://jsfiddle.net/roadprophet/DsvX6/3/

<div ng-app="myapp">
    <div ng-controller="TreeCtrl">
        <tree family="treeFamily">           
                <p>{{ family.name }}</p>
        </tree>
    </div>
</div>

 template:       
            '<ul>' + 
                '<li ng-transclude></li>' +
                '<li ng-repeat="child in family.children">' +
                    '<tree family="child"><div ng-transclude></div></tree>' +
                '</li>' +
            '</ul>'

1
一个例子可以在https://dev59.com/eG3Xa4cB1Zd3GeqPg6It#14512006中找到。在我的情况下,递归导致它无法工作。我试图获得的优势是为同一指令拥有多个模板,并且易于维护(比在JS中更容易更改模板代码)。 - bsr
新的“编辑”有意义吗?只是将传递的模板作为树指令递归使用的模板进行重用。看起来你可以根据自定义属性随意构建家庭结构。而且模板可以根据树的不同用途进行定制。 - Erstad.Stephen
如果有必要的话,我也可以更详细地解释为什么这样做是有意义的。 - Erstad.Stephen
1
Stephen。太好了,我想这正是我在寻找的。让我进行更多测试,然后再与您联系。谢谢。 - bsr
让我们在聊天中继续这个讨论。点击此处进入聊天室 - bsr
显示剩余3条评论

1

您希望将插入的DOM与父级作用域编译; 您可以在指令控制器定义中使用可注入的$transclude函数自动执行此操作:

module.directive("tree", function($compile) {
  return {
    restrict: "E",
    transclude: true,
    scope: { family: '=' },
    template: '<ul>' + 
                '<li ng-repeat="child in family.children">' +
                  '<tree family="child">' +
                    '<p>{{ child.name }}</p>' +
                  '</tree>' +
                '</li>' +
              '</ul>',
    controller: function($element, $transclude) {
      $transclude(function(e) {
        $element.append(e);
      });
    },
    compile: function(tElement, tAttr, transclude) {
      var contents = tElement.contents().remove();
      var compiledContents;
      return function(scope, iElement, iAttr) {
        if(!compiledContents) {
          compiledContents = $compile(contents);
        }
        compiledContents(scope, function(clone) {
          iElement.append(clone);
        });
      };
    }
  };
});

这样您就可以在根模板中使用parent作用域属性treeFamily(还要注意指令模板中的child的使用):
<div ng-app="myapp">
  <div ng-controller="TreeCtrl">
    <tree family="treeFamily">
      <p>{{ treeFamily.name }}</p>
    </tree>
  </div>
</div>

您可以在此处查看示例:http://jsfiddle.net/BinaryMuse/UzHeW/

1
Brandon,谢谢。但是,'<p>{{ child.name }}</p>'输出的是树的名称(这是指令的一部分)。这样就失去了它的意义。我实际上想要将其放在html中。这是一个简单的情况,但我可能会在html中添加更多标记。我只需要在html中访问树“node”,并针对此渲染在html中定义的标记。因此,我可以重用指令,但轻松更改html中包含的标记。我认为这是传输的目的之一。无论如何,如果我从指令中删除标记,则不会输出任何内容。http://jsfiddle.net/UzHeW/8/ 如果我还没有表达清楚,请告诉我。 - bsr

0

来晚了,但我需要这个项目,所以在研究了其他很棒的方法和方向后,最终想出了这个:

ng-transclude 指令相同的代码,但增加了一个小的 context 绑定,指令会监视并在每次更改时设置传递生成的作用域上的值。与 ng-repeat 相同,但这样可以实现:

  1. 使用自定义 ng-transclude 与 ng-repeat,无需重写 ng-repeat 和像 angular/2 模板输出。
  2. 在接收来自父级的直接上下文数据的同时,使传递的内容保持访问祖先作用域。再次与模板输出相同。

增强的 ng-transclude 函数:

return function ngTranscludePostLink(
   ...
  ) {
  let context = null;
  let childScope = null;
  ...
  $scope.$watch($attrs.context, (newVal, oldVal) => {
    context = newVal;
    updateScope(childScope, context);
  });
  ...
  $transclude(ngTranscludeCloneAttachFn, null, slotName);
  ...
  function ngTranscludeCloneAttachFn(clone, transcludedScope) {
     ...                                 
     $element.append(clone);
     childScope = transcludedScope;
     updateScope(childScope, context);
     ...
  }
  ...
  function updateScope(scope, varsHash) {
    if (!scope || !varsHash) {
      return;
    }
    angular.extend(scope, varsHash);
  }
}

它的用法:

应用程序

<my-list items="$ctrl.movies">
   <div>App data: {{ $ctrl.header }}</div>
   <div>Name:{{ name }} Year: {{ year }} Rating: {{ rating 
          }}</div>
</my-list>

我的列表

<ul>
  <li ng-repeat="item in $ctrl.items track by item.id">
   <div>Ng repeat item scope id: {{ $id }}</div>
   <cr-transclude context="item"></cr-transclude>
  </li>
</ul>

完整的指令代码可以在GitHub上这里找到


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接