Angular.js: ng-transclude不应创建新的同级作用域。

创建于 2013-12-20  ·  69评论  ·  资料来源: angular/angular.js

这更多是一个变更请求,我想看看其他人的想法。

以我的拙见,ng-transclude不应创建它自己的作用域,或者至少应该有一种方法阻止它这样做。 其背后的原因是,请求包含的指令已经具有指定其是否要具有作用域或孤立作用域或根本不希望作用域的手段。 它使用ng-transclude指令标记要插入内容的位置。 当ng-transclude创建自己的同级作用域时,它会破坏指令定义的期望,该指令定义了它想要的作用域,并且出现了流行的“值”与“对象。值”混淆。

这是我认为新范围没有意义的示例:

ui.directive('box', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div ng-transclude/>',
        replace: true,
        scope: {}
    };
});

该指令所需的全部就是用<box>content</box>替换<div>content</div> ,并使内容具有孤立的作用域。

像这样创建指令的嵌套结构会导致作用域树污染。 这是一个三个示例的嵌套示例(http://plnkr.co/edit/DwukVGGprFFjQuVY8yTz),它们创建了如下的作用域树结构:

< Scope (002) : ng-app
    < Scope (003) : ng-controller
        < Scope (004) : box
        < Scope (005) : ng-transclude
            < Scope (006) : box
            < Scope (007) : ng-transclude
                < Scope (008) : box
                < Scope (009) : ng-transclude

这种行为似乎并没有为其目的增加任何价值,但是在初学者之间造成了很多困惑。

目前,我使用以下变通办法,可以完全实现前面的示例所做的工作:

ui.directive('box', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div/>',
        replace: true,
        scope: {},
        link: function(scope, element, attrs, transclude) {
            transclude(scope.$parent, function(content) {
                element.append(content);
            });
        }
    };
});

这是一个小例子(http://plnkr.co/edit/46v6IBLkhS71L1WbUDFl),它说明了这个概念。 它使作用域树变得整洁:

< Scope (002) : ng-app
    < Scope (003) : ng-controller
        < Scope (004) : box
        < Scope (005) : box
        < Scope (006) : box

当用户绑定“值”而不是“ object.value”时,双向绑定的工作方式与许多人期望的一样。 (我相信,仅传递“值”在某些情况下有效,而在另一些情况下则无效,并且在javascript中指责原型继承的性质并不是一个很好的借口。许多人发现此行为出乎意料,这一事实表明存在体系结构缺陷)

我很想听听其他人的想法和用例,他们认为为ng-transclude创建新的同级作用域是有意义的。

Lots of comments $compile high won't fix bug

最有用的评论

好几年前我改用Ember。 :)

所有69条评论

超越在哪里创建新的作用域? http://plnkr.co/edit/EuHaBR26JgAegQKvwOGH?p=preview我没看到

我说的是ng-transclude指令。 您的示例中所包含的正是我的解决方法。

这是一个有效的请求。 我们考虑将其用于1.2,但已接近最终版本,并且不想引入这一重大更改。

我们应该考虑1.3

好一个! 很高兴你们已经考虑过了。

为此+1。 我认为我的问题与此有关:我在带有表单的指令中使用ng-transclude,并且必须通过scope。$$ childHead访问表单验证对象,但是访问模型没有问题。

这是一个示例: http :

+1今天遇到了这个问题,我讨厌到处乱扔$parent

因此,为了找到解决方案,似乎有两种可能性

1)更改ngTransclude指令以指定其范围(实际上,它可以缩小得更多,我认为---不需要控制器)

或者

2)在未指定范围的情况下不要创建新的范围

因此,我们可以使用选项1)保存一些字节,这很好,2)是最小的解决方案(大约3行删除),而且我不清楚在某些情况下在其中隐式创建新作用域是有意义的(也许有,但是似乎完全与文档中描述的包容方式相反)


或者,如果您想成为超级用户,则可以通过允许ngTransclude通过属性值指定是否需要新作用域来避免完全破坏更改。

有什么想法吗?

我没有得到您的1和2之间的差额!

只是我的意见,但我认为向后兼容对于此更改很重要。 我想象有很多应用程序(包括我的)通过使用$ parent和scope。$$ childHead之类的方法解决了这个范围问题。 更改此行为的任何更新都会引起一些头痛(但最好早点头痛比晚点头痛)。

就是说,从理论上讲,我认为ng-tranclude默认具有与指令相同的作用域更有意义。 包含内容的重点是您希望它是内容的无缝部分。 有时候,我有很多嵌套的带有伪指令的指令,但是我仍然希望它们表现为一个大组件。 让它们具有所有不同的作用域会使这变得非常棘手。

只是我的想法。 至少,只有这种选择会比当前情况高出一步。 :)

@troch进行澄清,我们将以下代码注入ngTranscludeDirective的控制器中:

        // This is the function that is injected as `$transclude`.
        function controllersBoundTransclude(scope, cloneAttachFn) {
          var transcludeControllers;

          // no scope passed
          if (arguments.length < 2) {
            cloneAttachFn = scope;
            scope = undefined;
          }

          if (hasElementTranscludeDirective) {
            transcludeControllers = elementControllers;
          }

          return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers);
        }

该指令不带范围地调用此函数,因此,范围是未定义的...然后在boundTranscludeFn ,如果transcludeScope是falsy,它将创建一个新的范围...

因此,我要说的是,对于1),我们可以简单地指定transclude函数的当前作用域(由于此指令是任何可能具有隔离作用域的对象的相邻指令,因此仍应为我们提供原始作用域) 。

或者,2),不要创建新的作用域,而只是默认使用当前作用域(在createBoundTranscludeFn中)(可能破坏更改,并可能破坏大量测试)。

两者都很简单。

+1

+1

+1

绝对+1

+1

+1

+1

请+1

您知道什么会很酷,尽管可能无法在1.3之前及时完全实现,但ES6代理可以使转换真的非常好---与转换范围的属性保持一致,但在层次结构中具有正确的位置。

如果代理实现不可用,则可能只是回退到scope。$ new(),因此实际上有可能使这项工作尽早进行。 棘手的是,该规范有点不稳定。

因此,如果您希望范围层次结构非常整洁,您仍然会得到不需要的范围,但至少会产生数据绑定不会意外中断的副作用。 我不知道。

+1

+1

+1

+1

+1

@caitp

或者,如果您想成为超级用户,则可以通过允许ngTransclude通过属性值指定是否需要新作用域来避免完全破坏更改。

凡是没有重大更改的内容,都请我投票。

+1

+1

+1

+1

+1

相关: https :

+1。 我一直想知道这种行为。 我喜欢caitp的解决方案:

@caitp

或者,如果您想成为超级用户,则可以通过允许> ngTransclude通过属性值指定是否需要新作用域来避免完全破坏更改。

+1

+1

+1

对于那些关注此问题的人,我创建了一个“改进的” ng-transclude指令,该指令的属性值定义了所追求的内部范围,并且可以是以下三种之一:

  • 填充-包含内容的范围是发生包含的元素的同级对象。 这就是当前的ng-transclude行为。
  • 父级-包含内容的范围是发生包含内容的元素的范围。
  • 子级-被排除的内容范围是子对象范围,即发生了被包括的元素的范围。

用法示例:

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

完整的例子

有关所有示例,请参见此小节

输出看起来像这样:

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;
      }
    }
  }
})

由于我早在#8609中提出的问题已作为此线程的副本被关闭,因此我在这里重述。

在我看来,当前为DOM的被包含部分创建范围的方式非常不合逻辑!
它与正常流量成一定角度,应予以固定!

这是我上一期的摘录:

我创建了一个小插件来说明我的问题。

主要罪犯在该指令中

     function pane() {
        return {
           restrict: 'E',
           transclude: true,
           scope: {
              title: '@'
           },
           template: '<div style="border: 1px solid black;">' +
              '<div style="background-color: gray">{{title}} (isolate scope id: {{$id}})</div>' +
              '<ng-transclude></ng-transclude>' +
              '</div>'
        };
     }

用户执行以下操作时:

<form>
    <pane title='enter your name'>
         <input type='text ngModel='username'>
    </pane>
    <pane title='enter your token'>
         <input type='text ngModel='token'>
   </pane>


对于许多用户,尤其是新用户,结果将是令人惊讶的。 这甚至是一个(过度)简化的用例。 尝试在此处显示一些验证消息;)

这是与此问题开始时不同的用例,但我同意它基本上是相同的问题!

相反,用dom元素来覆盖范围更有意义。 如果确实需要新的作用域,则用户可以将ngController放在ngTransclude元素上。 或者在ngTransclude上可以有一个可选标志来触发一个。 该标志也可以用于解决#5489用例。 并且它也可以用于提供的解决方案caitp。

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1似乎被包含在内的伪指令创建了作用域,即使系统不要求这样做也是如此。 示例http://plnkr.co/edit/Wn81IBkE87vtigXvjmIa?p=preview

+1

+1

+1

+1

+1

+1

+1

+1

+1-有任何有效的解决方法吗?

@nikkwong ,此线程中发布了一些解决方法。 我知道我确实链接显示解决方法

好的,所以即使到达了,也要等到1.5.x

但在此之前,我有一个担忧。 创建新作用域(称为隔离作用域的同级兄弟,或更准确地说是被包含内容的原始作用域的子代)的主要原因是为了防止内存泄漏。

比较一下这个矮子:
http://plnkr.co/edit/3NVxdYGy1AFDvD0M2BYI?p=预览

使用可重新使用原始包含范围的作用域的版本:
http://plnkr.co/edit/MXFz2awcqwQQ7R882Xwz?p=preview

在第二个版本中,当您打开和关闭被隐藏的内容时,观察者的数量不断增加-内存泄漏。

@petebacondarwin它绝对应该是一个新范围,以便可以由其观察者破坏。 我认为,对包含的大多数忧虑是因为ng-transclude将创建包含作用域的同级作用域,从而使得无法通过原型继承来访问其变量。 还是我错了?

我们认为问题在于角度2方式绑定的工作方式,确切地说是ng-transclude创建子范围,但是在很多情况下,您会在相同的情况下发现自己,例如ng-repeat创建子范围。 在查看了角度代码后,我们意识到2向绑定问题不适用于object属性,但可以与object本身一起很好地工作

问题: http :

解决方案: http :

它不是完美的,但是知道这解决了我们的很多问题,永远不要对属性进行双向绑定

请注意,这种解决方案已经由mbykovskyy提出,当他提出问题时,我仅举一个例子,因为花了我一段时间才弄清楚。

@petebacondarwin这只是暂时性的泄漏,直到持有范围被破坏为止。 这是(一小撮)[http://plnkr.co/edit/Q587WQnX0u0u7JjhtCxa?p=preview]显示,如果您关闭transclude-holding-scope,一切都会被释放。
仍然是需要注意的一点。 也许文档中有关此可能泄漏的大量警告可能足以解决此问题?

好吧,直到您刷新浏览器,任何JS泄漏都是暂时的;-)
2015年9月8日16:41,“ Sander Elias” [email protected]写道:

@petebacondarwin https://github.com/petebacondarwin这只会是
暂时泄漏,直到保留范围被破坏为止。 这里有一个
plunk)[http://plnkr.co/edit/Q587WQnX0u0u7JjhtCxa?p=preview]显示
如果您关闭了transclude-holding-scope,一切都会变得
发布就好了。
仍然是需要注意的一点。 也许在
关于这种可能的泄漏的文档可能足以解决它?

-
直接回复此电子邮件或在GitHub上查看
https://github.com/angular/angular.js/issues/5489#issuecomment -138603298

@SanderElias Angular应用程序的用户
我们已经看到内存的使用量每天都在增加,并且我们在页面上放置的内容必须非常小心,引入更多可能的泄漏是有风险的。

@troch-包含实际上创建原始范围内包含被包含内容的范围的子范围。 这被打断了几个版本(肯定是在1.2中),而只是创建了当前指令范围的父级的子级。 这意味着深度嵌套的包含实际上具有错误的包含范围。

我必须同意@petebacondarwin ,即使当前行为不是100%直观的,当前行为也能最好地防止泄漏。 我倾向于解决此问题,因为“无法修复”

好几年前我改用Ember。 :)

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

相关问题

jtorbicki picture jtorbicki  ·  3评论

guyandtheworld picture guyandtheworld  ·  3评论

visnup picture visnup  ·  3评论

ashclarke picture ashclarke  ·  3评论

ceymard picture ceymard  ·  3评论