在AngularJS中编写指令时,如何确定是否不需要新的作用域,新的子作用域或新的隔离作用域?


265

我正在寻找一些指南,可以用来帮助确定编写新指令时要使用的范围类型。理想情况下,我想要类似于流程图的内容,该流程图将引导我解决一系列问题并弹出正确的答案-没有新的新作用域,新的子作用域或新的孤立作用域-但这可能要求太多。这是我目前微不足道的指导原则:

我知道在某个元素上使用具有隔离范围的指令会强制同一元素上的所有其他指令使用相同的(一个)隔离范围,因此在可以使用隔离范围时这不会严格限制吗?

我希望Angular-UI团队中的一些人(或编写了许多指令的其他人)可以分享他们的经验。

请不要添加简单的回答“对可重用组件使用隔离范围”。


“子作用域”是指通过“ scope。$ new()”在链接函数中创建作用域?因为我知道,指令可以具有隔离范围,也可以不具有隔离范围(因此将在使用范围内使用范围)
Valentyn Shybanov

3
@ValentynShybanov设置scope: true$scope.new()自动创建一个子作用域。
乔什·大卫·米勒

2
@ Valentyn,Josh所说的:因此,三种可能性是scope: false(默认值,没有新作用域),scope: true(原型继承的新作用域)和scope: { ... }(新隔离作用域)。
Mark Rajcok

1
是的,thnx。我错过了“ true”和“ {}”之间的区别。很高兴知道。
Valentyn Shybanov

还有人们通常容易忽略..第4情况下是“指令控制器”。我认为这个问题应扩大到包括他们以及... +1的问题..
ganaraj

Answers:


291

真是个好问题!我很喜欢听别人怎么说,但这里是我使用的指导方针。

高海拔前提:作用域用作我们在父控制器,指令和指令模板之间进行通信的“胶水”。

父范围: scope: false,因此根本没有新范围

我不经常使用它,但是正如@MarkRajcok所说,如果该指令不访问任何范围变量(并且显然没有设置任何变量!),就我而言,这很好。这对于在父指令的上下文中使用的子指令(尽管总是有例外)并且没有模板也很有用。基本上,带有模板的任何东西都不属于共享范围,因为您本质上是在为访问和操作公开该范围(但是我敢肯定,此规则有例外)。

例如,我最近创建了一个指令,该指令使用我在编写过程中使用的SVG库绘制(静态)矢量图形。它有$observe两个属性(widthheight),并在计算中使用这些属性,但它既不设置也不读取任何范围变量,也没有模板。这是不创建另一个范围的好用例。我们不需要一个,那为什么要麻烦呢?

但是,在另一个SVG指令中,我需要使用一组数据,并且还必须存储一点点状态。在这种情况下,使用父级作用域是不负责任的(同样,通常来说)。所以与其...

子范围: scope: true

具有子作用域的指令是上下文感知的,旨在与当前作用域进行交互。

显然,相对于隔离范围而言,此方法的主要优势在于,用户可以自由地对想要的任何属性使用插值;例如,class="item-type-{{item.type}}"在带有隔离范围的指令上使用默认情况下将不起作用,但在具有子范围的指令上使用则可以正常工作,因为默认情况下仍可以在父范围内找到内插的内容。同样,该指令本身可以在其自身范围内安全地评估属性和表达式,而不必担心对父代的污染或损坏。

例如,工具提示就是刚刚添加的内容;孤立范围将不起作用(默认情况下,请参见下文),因为期望在此使用其他指令或内插属性。工具提示只是一个增强。但是工具提示还需要在作用域上设置一些要与子指令和/或模板一起使用的东西,并且显然要管理其自身的状态,因此使用父作用域的确非常糟糕。我们要么在污染它,要么在破坏它,布宜诺斯艾利斯也不是。

我发现自己比隔离范围或父范围更经常使用子范围。

隔离范围: scope: {}

这是针对可重用组件的。:-)

但是认真地说,我认为“可重用组件”是“自包含组件”。目的是将它们用于特定目的,因此将它们与其他指令组合或将其他内插属性固有地添加到DOM节点是没有意义的。

更具体地说,通过在父作用域的上下文中评估的指定属性,可以提供此独立功能所需的任何内容。它们可以是单向字符串('@'),单向表达式('&')或双向变量绑定('=')。

在独立组件上,不需要在其上应用其他指令或属性,因为它本身就存在。它的样式由其自己的模板(如有必要)控制,并且可以包含适当的内容(如有必要)。它是独立的,因此我们将其放在一个单独的范围内也可以说:“不要对此烦恼。我通过这几个属性为您提供了已定义的API。”

一个好的最佳实践是从指令链接和控制器功能中排除尽可能多的基于模板的内容。这提供了另一个“类似于API”的配置点:指令的用户可以简单地替换模板!功能都保持不变,并且其内部API从未被使用过,但是我们可以根据需要尽可能多地使用样式和DOM实现。ui / bootstrap是一个很好的例子,因为Peter&Pawel很棒。

隔离范围也非常适合与包含一起使用。带标签;它们不仅是全部功能,而且其内部的所有内容都可以在父范围内自由评估,同时让选项卡(和窗格)执行所需的任何操作。选项卡显然具有它们自己的状态,该状态属于作用域(与模板进行交互),但是该状态与使用它的上下文无关-它完全是使选项卡指令成为选项卡指令的内部。此外,在选项卡中使用任何其他指令没有多大意义。它们是选项卡-我们已经有了该功能!

将其包含更多功能或包含更多功能,但该指令已经存在。

综上所述,我应该注意,有一些方法可以解决隔离范围的某些限制(即功能),就像@ProLoser在他的回答中所暗示的那样。例如,在子作用域部分中,我提到了使用隔离作用域时默认情况下非定向属性中断的插值。但是,例如,用户可以简单地使用class="item-type-{{$parent.item.type}}"它,它将再次起作用。因此,如果迫切需要在子作用域上使用隔离作用域,但是您担心其中一些限制,请知道您可以在需要时解决所有这些限制。

摘要

没有新作用域的指令为只读;他们是完全值得信赖的(即应用程序内部的),并且它们不会碰插孔。具有子作用域的指令可添加功能,但它们并不是唯一的功能。最后,隔离范围是针对整个目标的指令;它们是独立的,因此可以让它们无赖(也是最“正确的”)。

我想打消我的初衷,但是当我想到更多事情时,我将对其进行更新。但这真是太糟糕了-这样的答案很久了...


PS:完全切线的,但是由于我们在谈论范围,所以我更喜欢说“原型”,而其他人则喜欢“原型”,这似乎更准确,但是根本无法奏效。:-)


谢谢乔什,很好的回答。我想要/期望长答案。我没有遵循的两件事:1)子作用域:“用户可以随意对想要的任何属性使用插值”。2)隔离范围:“或者,如果不是,则不是全部?”您能详细说明一下吗?(如果方便的话,可以随意编辑您的帖子,而不用发表评论。)
Mark Rajcok

@MarkRajcok对于(1),我对其进行了更改,以使其不太模糊-让我知道我是否失败。对于(2),这是打字错误和措辞不佳的结合;我改写了该段,以使其更清楚。我还添加了一个或两个额外的示例,阐明了更多内容,并修复了一些拼写错误。
Josh David Miller

正如答案中提到的那样,角度引导程序是将它们组合在一起的一个很好的例子。我发现特别有用的手风琴例如- 的GitHub -手风琴
平静

您提到您使用子范围最多,我认为指令的可重用模式是最常见的,并且我避免编写只能使用一次的指令。这是不需要的吗?有时,当我的HTML太大时,我想将该部分移到指令中,但是它只会被使用一次,因此我将其保留在html中。
user2483724 2014年

2
@ user2483724一个非常普遍的误解是“可重用”指令是使用隔离作用域的指令。不是。如果您看一下预包装的指令,几乎没有一个使用隔离作用域-有些甚至没有子作用域-但我向您保证它们是可重用的!规则应取决于如何使用伪指令中的范围。如果只是为了节省文件空间,我不确定指令是最好的方法。为了显影剂,这增加了处理时间。但是,如果您必须这样做,那就去吧。或使用ngInclude。或者将其作为构建的一部分。很多选择!
乔什·戴维·米勒

52

我的个人政策和经验:

隔离:私人沙箱

我想创建许多范围方法和变量,这些方法和变量仅由我的指令使用,并且用户从未看到或直接访问。我想将可用的范围数据列入白名单。我可以使用包含来允许用户跳回父作用域(不受影响)。我希望我的变量和方法在被隐蔽的孩子中访问。

子级:内容的一个小节

我要创建范围的方法和变量CAN用户访问,但是是不相关的我的指令的情况下外周边范围(兄弟姐妹和父母)。我也想让所有父范围数据透明地滴入。

无:简单的只读指令

我真的不需要弄乱范围方法或变量。我可能正在做一些与范围无关的事情(例如显示简单的jQuery插件,验证等)。

笔记

  • 您不应让ngModel或其他因素直接影响您的决定。您可以通过做ng-model=$parent.myVal(孩子)或ngModel: '='(孤立)之类的事情来规避奇怪的行为。
  • 隔离 + transclude都将正常行为恢复兄弟指令和返回父范围,所以不要让这些影响你的判断无论是。
  • 不要惹的范围没有因为它就像穿上范围的数据对DOM的下半部分,但不是上半部分,这使得0感。
  • 注意指令优先级(没有具体示例说明如何影响事物)
  • 注入服务或使用控制器跨任何范围类型的指令进行通信。您也require: '^ngModel'可以查看父元素。

1
我可能对这部分有误解:“隔离+覆盖将所有正常行为还原为同级指令”。见这个矮子。您必须查看控制台。
乔什·大卫·米勒

1
感谢ProLoser提供您的见解/答案。如果我添加了angularjs-ui标签,您就是我希望看到这篇文章的人之一。
Mark Rajcok

@JoshDavidMiller在谈论同一DOM元素上的指令时,事情变得更加复杂,您应该开始查看priority属性。隐含与孩子的内容更相关。
ProLoser

1
@ProLoser对,但是我不确定您所说的意思是什么。它们显然会影响子级,但是指令的范围将如何影响他们的兄弟指令?
乔什·大卫·米勒

18

在写了很多指令之后,我决定减少使用 isolated范围。即使它很酷并且您封装了数据并且确保不要将数据泄漏到父作用域,但它严重限制了可以一起使用的指令数量。所以,

如果您要编写的指令完全独立运行,并且不打算与其他指令共享,请使用孤立范围。(就像一个组件,您可以直接插入它,而无需为最终开发人员进行太多自定义)(当您尝试编写包含指令的子元素时,它将变得非常棘手)

如果您要编写的指令只是进行dom操纵,则不需要内部作用域状态,也不需要显式的作用域更改(大多数情况很简单);去没有新的范围。(如ngShowngMouseHoverngClickngRepeat

如果您要编写的指令需要更改父范围中的某些元素,但还需要处理某些内部状态,请使用新的子范围。(例如ngController

一定要检查指令的源代码:https : //github.com/angular/angular.js/tree/master/src/ng/directive
这对如何思考指令有很大帮助


如果多个组件需要相互通信,它们可以具有隔离的范围和使用require,因此,您的指令仍应保持解耦状态。那么如何限制可能性呢?它甚至使指令更具体(因此声明您所依赖的东西)。因此,我只留下一条规则:如果您的指令具有状态或需要使用它的作用域中的某些数据,请使用隔离的作用域。否则不要使用范围。关于“子作用域”,我也写了很多指令,从不需要此功能。如果“需要更改父范围中的某些元素”,请使用绑定。
Valentyn Shybanov

还有关于“需要更改父范围中的某些元素”的信息-如果您在子范围中进行了修改,则更改不会填充到父范围中(除非您使用肮脏的$parent技巧)。所以实际上是“子作用域”的指令是什么,貌似应该够用后-喜欢ngRepeat创建新的子作用域为每个项目重复(但是也带来了使用它scope.$new();,而不是scope: true
Valentyn Shybanov

1
您不能在同一个元素中要求多个隔离的作用域,也不能访问父作用域中的函数,除非您明确绑定它们。(好运ngClick等)。我同意,要求创建了一种解耦,但是您仍然需要了解父指令。除非它像一个组件,否则我反对隔离。指令(至少其中的大多数指令)具有很高的可重用性,而隔离则打破了这一点。
UmurKontacı13年

我也没有使用子范围的in指令,但是由于子范围的原型通常是从​​父范围继承的,因此,如果访问父范围的属性中的某个属性,则会填充更改。Angular的作者在MTV聚会中谈到了这一点,“在某个地方放点不错” youtube.com/watch?v=ZhfUv0spHCY
UmurKontacı13年

5
首先,我认为您对隔离示波器有些苛刻。我认为它们的适用性比您给予他们的荣誉更广,并且有一些方法可以避免您(正确)指出的许多在使用它们时我们面临的挑战。我也不同意“最终开发人员的定制不多”-有关详细信息,请参见我的答案。就是说,您的回答既不错,也不错,它的确解决了这个问题,所以我不确定为什么它被否决了。因此,+ 1。
Josh David Miller

9

只是以为我会补充我目前的理解以及它与其他JS概念的关系。

默认值(例如,未声明或范围:false)

从哲学上讲,这等效于使用全局变量。您的指令可以访问父控制器中的所有内容,但也会影响它们并同时受到影响。

范围:{}

这就像一个模块,任何要使用的东西都需要显式传递。如果您使用的EVERY指令是一个隔离作用域,则可能等同于制作EVERY JS文件,而您自己编写了自己的模块,这在注入所有依赖项时会产生大量开销。

范围:儿童

这是全局变量和显式传递之间的中间立场。它类似于javascript的原型链,只是为您扩展了父作用域的副本。如果创建隔离作用域并传递父作用域的每个属性和功能,则它在功能上等效于此。


关键是ANY指令可以以任何方式编写。不同的作用域声明可以帮助您进行组织。您可以将所有内容制作为模块,也可以仅使用所有全局变量并非常小心。为了简化维护,尽管最好将逻辑模块化为逻辑上一致的部分,但在开放的草地和封闭的监狱之间要保持平衡。我认为这很棘手的原因是,当人们了解了这一点时,他们认为他们正在学习指令的工作方式,但实际上他们正在学习代码/逻辑组织。

帮助我弄清楚指令如何工作的另一件事是学习ngInclude。ngInclude帮助您包括html局部。当我第一次开始使用指令时,我发现可以使用它的模板选项来减少代码,但是我并没有真正附加任何逻辑。

当然,在angular的指令和angular-ui团队的工作之间,我还没有创建自己的指令,该指令可以做任何实质性的事情,因此我对此的看法可能是完全错误的。


2

我同意乌穆尔。从理论上讲,孤立的作用域听起来很棒且“可移植”,但是在构建我的应用程序以包含非平凡的功能时,我遇到了需要合并多个指令(一些嵌套在其他指令中或向其中添加属性)以便完全编写的指令的需求。拥有HTML,这是领域特定语言的目的。

最后,太奇怪了,不得不在指令的每个DOM调用上将每个全局值或共享值向下传递给具有多个属性的链(这是隔离作用域所必需的)。重复将所有内容写入DOM看起来很愚蠢,即使它们是共享对象,也感觉效率很低。它也不必要地使指令声明复杂化。使用$ parent来“到达”并从HTML指令中获取值的解决方法似乎很不好。

我也想改变我的应用程序,使其具有几乎没有隔离符的大部分子范围指令-仅那些不需要通过父级访问任何东西的对象,而无需通过简单的,非重复的属性进行传递。

几十年来,在梦想着领域特定语言的梦想之前,我很高兴AngularJS提供了这个选项,而且我知道,随着更多开发人员在该领域工作,我们将看到一些非常酷的应用程序架构师也很容易编写,扩展和调试。

-D

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.