AngularJS中带有ng-repeat范围的指令隔离范围

Posted

技术标签:

【中文标题】AngularJS中带有ng-repeat范围的指令隔离范围【英文标题】:Directive isolate scope with ng-repeat scope in AngularJS 【发布时间】:2013-03-15 11:01:08 【问题描述】:

我有一个带有隔离作用域的指令(以便我可以在其他地方重用该指令),当我将此指令与 ng-repeat 一起使用时,它无法工作。

我已阅读有关此主题的所有文档和 *** 答案并了解这些问题。我相信我已经避免了所有常见的陷阱。

所以我知道我的代码由于ng-repeat 指令创建的范围而失败。我自己的指令创建了一个隔离作用域,并对父作用域中的对象进行双向数据绑定。我的指令将为这个绑定变量分配一个新的对象值,当我的指令在没有ng-repeat 的情况下使用时,这可以完美地工作(父变量已正确更新)。但是,对于ng-repeat,赋值会在ng-repeat 范围内创建一个新变量,而父变量看不到更改。根据我所阅读的内容,所有这些都符合预期。

我还读到,当给定元素上有多个指令时,只会创建一个范围。并且可以在每个指令中设置priority 来定义应用指令的顺序;指令按优先级排序,然后调用它们的编译函数(在http://docs.angularjs.org/guide/directive 搜索单词优先级)。

所以我希望我可以使用优先级来确保我的指令首先运行并最终创建一个隔离范围,并且当ng-repeat 运行时,它重新使用隔离范围而不是创建一个典型的范围从父作用域继承。 ng-repeat 文档指出该指令以优先级 1000 运行。尚不清楚1 是较高优先级还是较低优先级。当我在指令中使用优先级1 时,并没有什么不同,所以我尝试了2000。但这让事情变得更糟:我的双向绑定变成了undefined,而我的指令没有显示任何内容。

我创建了a fiddle to show my issue。我已经在我的指令中注释掉了priority 设置。我有一个名称对象列表和一个名为name-row 的指令,它显示名称对象中的名字和姓氏字段。单击显示的名称时,我希望它在主范围内设置 selected 变量。名称数组,selected 变量使用双向数据绑定传递给 name-row 指令。

我知道如何通过在主范围内调用函数来使其工作。我也知道,如果selected 在另一个对象内部,并且我绑定到外部对象,那么一切都会起作用。但我目前对这些解决方案不感兴趣。

相反,我的问题是:

如何防止 ng-repeat 创建一个原型从父范围继承的范围,而是让它使用我的指令的隔离范围? 为什么我的指令中的优先级 2000 不起作用? 使用 Batarang,是否可以知道正在使用的示波器类型?

【问题讨论】:

通常情况下,如果您的指令将与其他指令在同一元素上使用,您不希望使用隔离范围。由于您正在创建自己的范围属性,并且您需要使用 ng-repeat,因此我建议您使用 scope: true 作为您的指令。另请参阅(如果您还没有)***.com/questions/14914213/… 另外,仅仅因为指令将在多个地方使用并不意味着我们应该自动使用隔离范围。 我已经阅读了您的许多答案(它们非常出色,感谢您撰写它们),但我从未想过阅读您的问题 :-)。我读了你链接的内容。在我看来,隔离范围指令不能与其他指令混合。我同意这样的指令是组件的观点,因此它们不需要与其他指令混合。对我来说,一个例外(到目前为止)是ng-repeat。我认为能够将独立指令与ng-repeat 混合使用是很有价值的。待续…… 从上面继续......所以如果应该只有一个指令具有一个元素的范围,那么ng-repeat 不应该有一个范围。 ng-repeat 有一个范围对于典型的用例来说确实有意义,所以我不建议改变它。相反,就像我在 Alex Osborn 的回答中评论的那样,我想我会创建一个基于 ng-repeat 的重复指令,它不会创建自己的范围。然后,这可以用于重复具有自己的隔离范围的指令。待续…… 重复指令的代码现在需要知道是使用ng-repeat 还是自定义无作用域重复指令。我认为“调用者”知道这一点是可以的,但“被调用者”(被重复的指令)知道它是否被重复是不行的。待续…… 对这里的 cmets 有点疯狂... :-) ngRepeat 必须 创建自己的范围。为什么你觉得你需要一个隔离作用域? 【参考方案1】:

好的,通过上面的很多cmets,我发现了困惑。首先,澄清几点:

ngRepeat 不会影响您选择的隔离范围 传递给 ngRepeat 以用于指令属性的参数确实使用原型继承范围 您的指令不起作用的原因与隔离范围无关

以下是相同代码的示例,但删除了指令:

<li ng-repeat="name in names"
    ng-class=" active: $index == selected "
    ng-click="selected = $index">
    $index: name.first name.last
</li>

这是一个JSFiddle,表明它不起作用。您会得到与指令中完全相同的结果。

为什么它不起作用?因为 AngularJS 中的作用域使用原型继承。父作用域上的值selected原始。在 javascript 中,这意味着当孩子设置相同的值时,它将被覆盖。 AngularJS 作用域中有一条黄金法则:模型值应该始终在其中包含.。也就是说,它们不应该是原语。请参阅this SO answer 了解更多信息。


这是示波器最初的样子。

单击第一项后,范围现在如下所示:

请注意,在 ngRepeat 范围内创建了一个新的 selected 属性。控制器范围 003 未更改。

当我们点击第二个项目时,你大概可以猜到会发生什么:


所以您的问题实际上根本不是由 ngRepeat 引起的——它是由违反 AngularJS 中的黄金法则引起的。修复它的方法是简单地使用对象属性:

$scope.state =  selected: undefined ;
<li ng-repeat="name in names"
    ng-class=" active: $index == state.selected "
    ng-click="state.selected = $index">
    $index: name.first name.last
</li>

这里有一个second JSFiddle 也展示了这个作品。

这是范围最初的样子:

点击第一项后:

这里,控制器作用域受到影响,正如所愿。

另外,为了证明这仍然适用于具有隔离范围的指令(因为,再次,这与您的问题无关),这里也是一个 JSFiddle ,视图必须反映对象.您会注意到,唯一必要的更改是使用 object 而不是 primitive

最初的作用域:

点击第一项后的作用域:

总结:再一次,您的问题不在于隔离范围,也不在于 ngRepeat 的工作方式。您的问题是您违反了已知会导致此问题的规则。 AngularJS 中的模型应该总是有一个.

【讨论】:

我对带有隔离作用域和 2 路数据绑定的指令的实验使我相信 . 黄金法则仅适用于具有原型继承的作用域。使用隔离作用域,您感兴趣的变量在指令的scope: 定义中定义,因此这些变量从一开始就存在于隔离作用域中。此外,它们不会屏蔽父变量,因为隔离作用域在原型上不是从父作用域继承的。即没有要屏蔽的“父”范围。 还应该说:你的指令没有创建子作用域,但它可以很容易地在 的上下文中使用需要 一个子范围。 ngRepeat 就是一种情况。嵌入也是如此。相信我 - 使用. Discussion on the AngularJS Google Group @JoshDavidMiller 终于解开了我的困惑! 你们这些很酷的图表是从哪里得到的?我也看到另一个用户发布了这些。 @Asad,我最近把这个工具放到了 GitHub 上,它叫Peri$scope。【参考方案2】:

不要直接试图避免回答你的问题,而是看看下面的小提琴:

http://jsfiddle.net/dVPLM/

关键点是,您可以构建指令以使用ng-repeat,而不是试图覆盖它,而不是试图对抗和改变 Angular 的传统行为。

在您的模板中:

    <name-row 
        in-names-list="names"
        io-selected="selected">
    </name-row>

在你的指令中:

    template:
'        <ul>' +      
'            <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
'                <a ng-click="setSelected($index)">' +
'                    $index - name.first name.last' +
'                </a>' +
'            </li>' +
'        </ul>'

回答您的问题:

ng-repeat 将创建一个范围,您真的不应该尝试更改它。 指令中的优先级不仅仅是执行顺序 - 请参阅:AngularJS : How does the html compiler arrange the order for compiling? 在 Batarang 中,如果您检查性能选项卡,您可以看到为每个范围绑定的表达式,并检查这是否符合您的预期。

【讨论】:

感谢您的快速回复。如果我的尝试违背了规律,我会有点难过(AngularJS 仍然是一个很棒的框架)。指令旨在成为可重用的组件。写的时候,作者不用担心会不会用ng-repeat。也许在第一次编写时,它从未与ng-repeat 一起使用。在未来的某个时间,它可能会与ng-repeat 一起使用,到那时,它应该可以在不重写的情况下工作。希望 AngularJS 的未来版本能够实现这一点。 我想澄清我上面的评论。我认为可以编写一个不担心它是否与ng-repeat 一起使用的指令。但似乎我必须将一个函数传递给指令,以便它可以修改父范围内的变量,而不是能够在指令自己的范围内改变双向绑定。双向绑定和可重用指令是我最喜欢 AngularJS 的两个方面,ng-repeat 被证明是美中不足。也许我可以写一个不创建自己的范围的ng-repeat 等价物。

以上是关于AngularJS中带有ng-repeat范围的指令隔离范围的主要内容,如果未能解决你的问题,请参考以下文章

在 angularjs 中使用 ng-repeat 时扩展 $index 的范围

Angularjs svg 图 ng-repeat 在范围内

AngularJs - 以相同名称的ng-repeat访问范围变量

数据进入时,Angularjs范围数组未显示

关于angularjs的ng-repeat指令

AngularJS在范围之间过滤