Angular.js: angular 1.2.18:transclude 的 ng-repeat 问题

创建于 2014-06-17  ·  48评论  ·  资料来源: angular/angular.js

当通过 ng-transclude 传递给指令时,您想要重复的引用 {{item}} 的 html 内容(通过指令中实现的 ng-repeat="item in collection")不适用于 1.2.18 版

http://embed.plnkr.co/EvzF25sPD3uZLQivDFqy/preview

最有用的评论

我最终做的只是使用$parent 。 这是香草的壁橱,无需添加太多东西。

所以我有类似的东西:

angular.module('test').directive('myDirectiveWithTransclusion', function() {
     return {
          restrict: 'E'
          transclude: {
               transcludeThis: 'transcludeThis'
          }
          template: "<div ng-repeat='item in array'><div ng-transclude='transcludeThis'></div></div>"
     }
})
<my-directive-with-transclusion>
     <transclude-this>
          {{$parent.item}}
     </transclude-this>
</my-directive-with-transclusion>

所有48条评论

哦,劳迪。 @petebacondarwin想再做一个吗? 这真的是“不要再次使用 ng-transclude 创建同级范围”的事情,只是由于损坏,它以前在这种情况下有效

(也许)相关: https :

可悲的是,这不是ng-transclude嵌入的工作方式。 您要做的是创建一个容器指令,该指令将其子代用作要在您自己的指令中删除的内容的“模板”。

过去我遇到过几次,并与 Misko 进行了长时间的辩论。 被嵌入的内容根据定义绑定到指令实例化位置的范围; 不属于指令模板的范围。

以前这可能有效,因为在某些涉及嵌套嵌入场景的情况下,我们实际上将嵌入绑定到错误的范围。

所以实际上你真的不需要在这里使用嵌入,因为你真正想做的只是将内部 HTML 注入到你自己的模板中。 您可以在 compile 函数中执行此操作,如下所示:

http://plnkr.co/edit/j3NwMGxkVRM6QMhmydQC?p=preview

app.directive('test', function(){

  return {
    restrict: 'E',
    compile: function compile(tElement, tAttrs, tTransclude) {

      // Extract the children from this instance of the directive
      var children = tElement.children();

      // Wrap the chidren in our template
      var template = angular.element('<div ng-repeat="item in collection"></div>');
      template.append(children);

      // Append this new template to our compile element
      tElement.html('');
      tElement.append(template);

      return {
        pre: function preLink(scope, iElement, iAttrs, crtl, transclude) {
            scope.collection = [1, 2, 3, 4, 5];
        },
        post: function postLink(scope, iElement, iAttrs, controller) {
          console.log(iElement[0]);
        }
      };
    }
  };
});

另一个例子(我相信):

索引.html:

<html ng-app='myApp'>

<head>
    <title>AngularJS Scopes</title>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
    <script src='index.js'></script>
</head>

<body ng-controller='myController'>
    <people>Hello {{person.name}}</people>
</body>
</html>

索引.js:

var myApp = angular.module( 'myApp', [] );

myApp.controller( 'myController', function( $scope ) {
    $scope.people = [
        { name: 'Rob'  },
        { name: 'Alex' },
        { name: 'John' }
    ];
});

myApp.directive( 'people', function() {
    return {
        restrict: 'E',

        transclude: true,
        template: '<div ng-repeat="person in people" ng-transclude></div>',
    }
});

适用于 Angular 1.2.1,但不适用于 1.2.18;

无辜的开发人员只能期望上面的代码能够工作。 这个文档说:

...有时希望能够传入整个模板而不是字符串或对象。 假设我们要创建一个“对话框”组件。 该对话框应该能够包装任意内容。

虽然ngTransclude 文档说:

标记使用嵌入的最近父指令的嵌入 DOM 的插入点的指令。 在插入嵌入的内容之前,将删除放置此指令的元素的任何现有内容。

这与@petebacondarwin 的定义

...创建一个容器指令,将其子项用作要在您自己的指令中删除的内容的“模板”

我真的不明白为什么嵌入在这里不是正确的解决方案。 如果有的话,我希望合理的解决方案涉及将模板范围注入到嵌入函数中。

不同之处在于嵌入的内容绑定到“外部”,即找到<people>元素的地方的范围。
而您想要的是将内联模板绑定到“内部”,即指令的范围。
如果您的指令没有创建自己的作用域,那么这大致相同。 如果您的指令创建了一个隔离范围,那么它绝对不是一回事。 内部作用域无法访问外部作用域。

我想您可以创建自己的指令,该指令确实将模板范围注入到嵌入函数中...

你说的很有道理——但前提是你了解了 Angular 的深度。 我的观点是,上面的例子应该可以在没有太多额外工作的情况下以某种方式工作。

在我看来,嵌入函数能够以某种方式访问​​模板(或“内部”)范围非常合理且非常实用。 我可以想到很多情况,为什么需要这样做。

这个博客中解释了同样的问题。 我认为越来越多的人会抱怨目前的情况(由于这种行为,Gi​​thub 上已经存在大量相关问题)。

非常感谢您的代码。 为了他人的利益,我在这里复制它:

var myApp = angular.module( 'myApp', [] );

myApp.controller( 'myController', function( $scope ) {
    $scope.people = [
        { name: 'Rob'  },
        { name: 'Alex' },
        { name: 'John' }
    ];
});

myApp.directive( 'people', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div ng-repeat="person in people" inject></div>'
    }
});

myApp.directive('inject', function(){
  return {
    link: function($scope, $element, $attrs, controller, $transclude) {
      if (!$transclude) {
        throw minErr('ngTransclude')('orphan',
         'Illegal use of ngTransclude directive in the template! ' +
         'No parent directive that requires a transclusion found. ' +
         'Element: {0}',
         startingTag($element));
      }
      var innerScope = $scope.$new();
      $transclude(innerScope, function(clone) {
        $element.empty();
        $element.append(clone);
        $element.on('$destroy', function() {
          innerScope.$destroy();
        });
      });
    }
  };
});

:-) 我同意嵌入不是一个容易理解的话题,如果没有大量的挠头和敲键盘。
也许我们需要围绕 ng-transclude 将嵌入的内容绑定到“外部”范围这一事实进一步澄清文档?

就个人而言,我觉得当前的文档,至少在这个关键页面上,相当明确和清晰:

这个 transclude 选项到底有什么作用? transclude 使具有此选项的指令的内容可以访问指令之外的范围而不是内部。

(以及接下来的一切都进一步说明了这一点)。

我会考虑添加您提供给框架的指令,可能将其标记为“ng-transclude-internal”? 我知道至少还有一个人尝试过这个,他的指令叫做“transscope”。

我已经尝试过解决方案,但我遇到了其他问题,为什么进入 ng-repeat 父作用域不是指令的作用域

http://plnkr.co/edit/7j92IC?p=preview

@luboid在您的 plunker 中

我将添加一个正确方法的示例,以使其按照您的预期工作。 (但是,总的来说,这仍然是一个非常糟糕的设计,没有充分的理由这样做)

实际上,想想看,使用 ng-repeat 或其他元素嵌入指令,您无法真正解决此问题。 所以是的,从 1.2.0 版开始就行不通了

@petebacondarwin这里有一个真正的新手问题。 为什么使用var innerScope而不仅仅是:

myApp.directive( 'inject', function() {
    return {
        link: function( $scope, $element, $attrs, controller, $transclude ) {
            if ( !$transclude ) {
                throw minErr( 'ngTransclude' )( 'orphan',
                    'Illegal use of ngTransclude directive in the template! ' +
                    'No parent directive that requires a transclusion found. ' +
                    'Element: {0}',
                    startingTag( $element ));
            }

            $transclude( $scope, function( clone ) {
                $element.empty();
                $element.append( clone );
            });
        }
    };
}); 

如果您要编译一些新元素,通常最好创建一个新范围,因为您不确定原始指令是否与一些也想创建范围等的复杂指令并置。但您可能能够得到把它放在这里...

感谢帮助,
我将使用外部模板($templateCache),然后事情变得有点简单,基本模板是用指令范围编译的

@Izhaki @petebacondarwin非常感谢包含指令。 终于从 1.2.16 更新到 1.2.20 花了一点时间才明白为什么我的应用程序开始如此艰难。

在我发现这个线程之前,我认为嵌入是一个非常简单的概念。 不。

感谢所有帮助解决这个问题的人。 由于这里描述的问题,更新到 1.2.21 破坏了我们的大部分界面,现在我们走上了正确的道路。

@caitp @petebacondarwin :如果我们能获得两条额外的信息会非常有帮助:

  1. “关于 1.2.0 版”(如 Caitlin 所说)发生的哪个重大变化导致这种方法突然爆炸? 我了解什么不再起作用,但我在该版本附近的变更日志中没有看到任何与此特定问题相关的特定内容。
  2. 如果我们想要独立的、可重用的容器,可以从中继承任意内部内容,最终用户应该采用什么替代方法来代替 ng-transclude? 示例:一个大型的多租户应用程序,其中的界面甚至是可变的并且完全由数据驱动。 所以,我们有像列表控件这样的东西,它们需要检索数据来填充自己,然后有一致的交互/行为。 我们希望能够标准化提供这些东西的包装器指令。 但是我们用于列表项(可能包含内部指令)的模板因情况而异。 并且列表通常基于生成它们的数据的树结构递归地嵌套。 因此,我们需要隔离那些嵌套列表。 我们确实控制了所有代码,因此我们不使用嵌入来隔离内部与外部。 相反,我们只是在对容器及其任意内容采用一种很好的声明式方法,正如指令指南中明确推荐的那样。 ng-include 现在是否是更好的选择(将内部模板的路径作为容器声明的属性传递),即使它让我们远离内联模板,因此感觉不那么惯用了?

我了解什么不再起作用,但我在该版本附近的变更日志中没有看到任何与此特定问题相关的特定内容。

https://github.com/angular/angular.js/blob/master/CHANGELOG.md#120 -timely-delivery-2013-11-08

我特别指的是这些

  • 仅将隔离范围传递给属于隔离指令 (d0efd5ee) 的子级
  • 使隔离范围真正隔离(909cabd3,#1924,#2500)

但是,我真的想不起来我在说什么了,谁知道ヽ༼ຈل͜ຈ༽ノ

在变更日志中,只有两种消息:变更和破坏性变更。 这不被视为重大更改,而是错误修复。 很难预料会有多少人使用这种行为。 但它可能应该进入迁移文档(需要一些爱)

Sho''nuff。 就是这些。 我正在寻找与嵌入相关的更改,但这显然是与嵌入相关的隔离范围的更改。 谢谢!

此更改也破坏了我们的代码。 有一些简单的声明方式来访问这些指令的person变量并在父作用域中使用它们会很好。 在看到在 1.2.18 中修复bug之前有多少人使用它之后,看起来这是一个常见的用例。

这是在 1.2.17 中有效并自 1.2.18 起损坏的演示

http://plnkr.co/edit/QswOxN?p=preview

@evgenyneu :假设您控制内部内容和容器指令,我们发现使用 ng-include 而不是 transclude 是清晰、简单的,并且为我们提供了我们想要的继承模式。 我们只是在外部指令声明中传递一个模板名称作为属性,并在我们以前拥有 transclude 指令的同一位置放置一个 ng-include 指令。 问题解决了。

如果您希望内部内容模板内联在视图模板上,那么只需使用 ng-template 将模板包裹在您之前使用的相同位置。

@xmlilley ,很好的提示,非常感谢。 这确实是最干净的方法。

这是演示: http :

伙计们,你知道你可以做到这一点,而不是右http://plnkr.co/edit/fw7thti1u4F9ArxsuYkQ?p=preview ---它并不完美,但它可以让你在那里

@caitp ,不错,谢谢。 我们有很多解决方案!

对于那些关注这个问题的人,我创建了一个“改进的”ng-transclude 指令,它的属性值定义了所寻求的内部范围,可以是 3 个之一:

  • silbing -嵌入的元素的同级范围。 这就是当前的 ng-transclude 行为。
  • parent - 嵌入的内容范围是发生嵌入的元素的范围。
  • child - 嵌入的内容范围是发生嵌入的元素范围的子范围。

用法示例:

template: 
  '<div ng-transclude="parent">' +
  '</div>'

完整示例

有关所有示例,请参阅此 plunk

输出如下所示:

image

来源

.config(function($provide){
    $provide.decorator('ngTranscludeDirective', ['$delegate', function($delegate) {
        // Remove the original directive
        $delegate.shift();
        return $delegate;
    }]);
})

.directive( 'ngTransclude', function() {
  return {
    restrict: 'EAC',
    link: function( $scope, $element, $attrs, controller, $transclude ) {
      if (!$transclude) {
        throw minErr('ngTransclude')('orphan',
         'Illegal use of ngTransclude directive in the template! ' +
         'No parent directive that requires a transclusion found. ' +
         'Element: {0}',
         startingTag($element));
      }

      var iScopeType = $attrs['ngTransclude'] || 'sibling';

      switch ( iScopeType ) {
        case 'sibling':
          $transclude( function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'parent':
          $transclude( $scope, function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'child':
          var iChildScope = $scope.$new();
          $transclude( iChildScope, function( clone ) {
            $element.empty();
            $element.append( clone );
            $element.on( '$destroy', function() {
              iChildScope.$destroy();
            });            
          });
          break;
      }
    }
  }
})

@Izhaki - FWIW,+10。 不会破坏当前的约定,但添加了一种自愿访问常见用例的干净、声明性的方式。 谢谢!

:+1: 对于@Izhaki的解决方案。 我将按原样使用它,但如果将其包含在 angular 中会感觉更舒服。

+1

@Izhaki +1,很好的例子!

感谢自定义 transclude 指令。 我最终只是制作了一个单独的自定义 transclude 而不是覆盖默认值。 您的补丁中有几个函数调用,我不确定它们来自哪里。 minErr() 和startingTag()

@dehru - 这些函数是 AngularJS 的内部函数。

@Izhaki非常感谢! 如果有人感兴趣,我已经分叉了重复嵌入内容的 plunk。
值得一提的是 ng-transclude="parent" 可能不会像您期望的那样工作。
http://plnkr.co/edit/S6ngqz?p=preview

plunker_ng-transclude_ng-repeat

@Izhaki非常好。
看起来应该在 angular 的拉取请求中。
至少在带有 Github 存储库的单独指令中,您是否认为可以发布它(以便我可以提供一些更改....)?
谢谢

伊扎基,这太棒了。 我_心_你。

@Izhaki这是一个很好的解决子作用域问题的方法。 谢谢。

我很困惑,想知道您是否可以解释一下,$transclude 函数的继承是如何从外部指令传递到 ngTransclude 指令的? 在我能找到的任何官方文件中都没有明确说明,但我认为必须在指令中使用transclude: true才能在链接函数中使用 $transclude 函数。 稍微玩了一下代码后,我发现使用transclude: true实际上破坏了代码。 这里发生了什么?

我为此抗争了好几天,事实上 transclude 会在 transclude 占位符中插入被嵌入的元素,而不是用它们替换它。

我发现 ng-transclude-replace 可以处理这两个问题! http://gogoout.github.io/angular-directives-utils/#/api/ng -directives-utils.transcludeReplace

我不需要将 ng-repeat 指令向下传播到重复指令本身或任何类似的东西! 一切似乎都在工作,包括所有验证/绑定。

这是我的代码片段:

<form-field label="Roles" required>
    <checkbox-group>
        <checkbox ng-repeat="role in roles" label="{{role.description}}">
            <input type="checkbox" name="selectedRoles" ng-model="role.selected" value="{{role.description}}" ng-required="!hasRole" />
        </checkbox>
    </checkbox-group>
</form-field>

@abobwhite感谢您提到 ng-

我最近遇到了这个问题。 虽然@Izhaki对我ng-transclude="sibling | parent | child"成为 angular core 的一部分?

@telekid - 我们目前没有计划将此功能包含在核心中。

我看到有解决方案和变通方法,但是如果有一种方法可以访问指令的内部范围,那将非常方便。

我想指出的是,我将@Izhaki创建的
分支: https :
公关: https :
Plunker: http ://plnkr.co/edit/5XGBEX0muH9CSijMfWsH?p=preview

我最终做的只是使用$parent 。 这是香草的壁橱,无需添加太多东西。

所以我有类似的东西:

angular.module('test').directive('myDirectiveWithTransclusion', function() {
     return {
          restrict: 'E'
          transclude: {
               transcludeThis: 'transcludeThis'
          }
          template: "<div ng-repeat='item in array'><div ng-transclude='transcludeThis'></div></div>"
     }
})
<my-directive-with-transclusion>
     <transclude-this>
          {{$parent.item}}
     </transclude-this>
</my-directive-with-transclusion>

嗨@moneytree-doug :我使用你提供的这个解决方案,但我发现 transclude-this 的范围仍然是指令范围,而不是由 ng-repeat 生成的新范围的子级。 你能给我一些建议吗?

@szetin你能放一个 jsfiddle 向我展示你的期望和正在发生的事情吗?

此页面是否有帮助?
0 / 5 - 0 等级