在AngularJS中使用ng-repeat作用域的指令隔离范围。

95

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

我已经阅读了所有关于此主题的文档和 Stack Overflow 的答案,并理解了其中的问题。我相信我已经避免了所有通常的陷阱。

因此,我知道我的代码失败是由于 ng-repeat 指令创建的作用域。我的自定义指令创建了一个隔离作用域并对父作用域中的对象进行了双向数据绑定。我的指令将为此绑定变量分配一个新的对象值,在没有 ng-repeat 的情况下它可以正常工作(父变量被正确更新)。然而,在使用 ng-repeat 时,该赋值会在 ng-repeat 作用域中创建一个新的变量,父变量无法看到这个变化,这都是预期的结果。

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

因此,我希望使用优先级,以确保我的指令首先运行并创建一个隔离作用域,当 ng-repeat 运行时,它将重复使用该隔离作用域,而不是创建一个从父作用域原型继承的作用域。 ng-repeat 文档表明该指令在优先级1000级别上运行。不清楚 1 是更高的优先级还是更低的优先级。当我在指令中使用 1 优先级时,它没有任何影响,所以我尝试了 2000。但是这使情况变得更糟:我的双向绑定变为 undefined,我的指令也没有显示任何内容。

我创建了一个演示我的问题的Fiddle。我已经在指令中注释掉了priority的设置。我有一个名字对象列表和一个名为name-row的指令,它显示名称对象中的名字和姓氏字段。当单击显示的名称时,我希望它在主作用域中设置一个selected变量。名称数组、selected变量均使用双向数据绑定传递给name-row指令。

我知道如何通过调用主作用域中的函数来使其工作。我也知道如果selected位于另一个对象内,并将其绑定到外部对象,则可以解决问题。但是我目前不感兴趣。

我的问题是:

  • 如何防止ng-repeat创建从父级作用域原型继承的作用域,并改为使用我的指令的隔离作用域?
  • 为什么我的指令中优先级2000没有起作用?
  • 是否可以使用Batarang知道正在使用哪种类型的作用域?

1
通常情况下,如果您的指令将与其他指令一起在同一个元素上使用,则不应使用隔离作用域。由于您正在创建自己的作用域属性,并且需要使用ng-repeat,我建议您为指令使用scope:true。另请参见(如果尚未)https://dev59.com/vWUp5IYBdhLWcg3w-LMJ。此外,仅因为指令将在多个位置使用并不意味着我们应该自动使用隔离作用域。 - Mark Rajcok
我已经阅读了您的许多回答(它们超出了卓越,感谢您撰写它们),但我从未想过阅读您的问题:-)。 我阅读了您链接到的内容。 在我看来,隔离范围指令不能与其他指令混合使用。 我同意这样的指令是组件,因此它们不需要与其他指令混合使用的观点。 对我来说唯一的例外(到目前为止)是ng-repeat。 我认为能够将独立指令与ng-repeat混合使用是很有价值的。 待续... - Deepak Nulu
在这里的注释有点疯狂... :-) ngRepeat 必须 创建它自己的作用域。你为什么觉得你需要一个隔离作用域呢? - Josh David Miller
@DeepakNulu 我认为您正在创建一个不必要的指令。显然,您尝试完成的任务可以通过ng-include和ng-repeat的组合实现。为每个重复项添加控制器即可轻松完成。 - ganaraj
有人能帮我解决这个问题吗?http://stackoverflow.com/questions/22000201/angularjs-directive-with-isolate-scope-ng-repeat - user1791567
显示剩余10条评论
2个回答

203

经过上面许多评论的研究,我发现了混淆点。首先,需要澄清几点:

  • 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作用域中有一个黄金法则:模型值应该总是带有一个.,也就是说,它们永远不应该是基本类型。欲知详情请参阅此SO答案


下面是作用域最初的情况图片:

enter image description here

点击第一项后,作用域现在看起来像这样:

enter image description here

请注意,在ngRepeat作用域上创建了一个新的selected属性。控制器作用域003没有被改变。

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

enter image description here


所以,你的问题实际上并不是由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>

这里有一个第二个 JSFiddle展示了这也可以工作。

这是作用域的初始外观:

输入图像描述

点击第一项后:

输入图像描述

在这里,控制器作用域正在受到影响,正如所期望的那样。

此外,为了证明这仍将与具有隔离范围的指令一起使用(因为,再次强调,这与您的问题无关),这里还有一个JSFiddle,视图必须反映对象。您会注意到唯一必要的更改是使用对象而不是原始数据类型

初始作用域:

输入图像描述

点击第一项后作用域:

输入图像描述

总之:再次强调,您的问题不在于隔离作用域,也不在于ngRepeat的工作方式。您的问题是违反了已知会导致此问题的规则。在AngularJS中,模型应始终具有 .


1
应该也要说:你的指令没有创建子作用域,但它很容易被用于需要子作用域的上下文中。ngRepeat就是一个例子。插入也是如此。相信我 - 使用 . - Josh David Miller
1
在AngularJS Google Group的讨论中,@JoshDavidMiller最终成功地解开了我的困惑! - Deepak Nulu
你们是怎么制作那些图表的?它们太棒了。 - Jeff Voss
2
你们这些酷炫的图表都是从哪里得来的?我也看到另一个用户发布了类似的内容。 - Asad Saeeduddin
3
@Asad,我最近在GitHub上发布了一个工具,它叫做Peri$scope。请您查看。 - Mark Rajcok
显示剩余6条评论

6

请看以下链接: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:HTML编译器如何安排编译顺序?
  • 在Batarang中,如果你检查性能选项卡,你可以看到每个作用域绑定的表达式,并检查是否符合你的期望。

感谢您的快速回复。如果我所尝试的与众不同,我会有点失望(AngularJS仍然是一个很棒的框架)。指令旨在成为可重用的组件。当编写它时,作者不应该担心它是否将与ng-repeat一起使用。也许当它首次编写时,它从未与ng-repeat一起使用。在将来的某个时候,它可能会与ng-repeat一起使用,在那时,它应该可以正常工作而无需重写。希望AngularJS的未来版本能够实现这一点。 - Deepak Nulu
我想澄清一下我上面的评论。我认为可以编写一个指令,不必担心它是否与 ng-repeat 一起使用。但是似乎我必须向指令传递一个函数,以便它可以修改父作用域中的变量,而不是能够在指令自己的作用域中改变双向绑定。双向绑定和可重用指令是我对 AngularJS 的两个最喜欢的东西,而 ng-repeat 正在成为一个问题。也许我可以编写一个等效于 ng-repeat 的指令,它不会创建自己的作用域。 - Deepak Nulu

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