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

When writing a directive in AngularJS, how do I decide if I need no new scope, a new child scope, or a new isolated scope?

我正在寻找一些指导方针,可以用来帮助确定在编写新指令时要使用哪种类型的范围。理想情况下,我想要类似于流程图的内容,该流程图引导我完成一系列问题并弹出正确的答案–没有新的新范围、新的子范围或新的隔离范围–,但这可能要求太多。以下是我目前的一系列指导方针:

  • 如果将使用该指令的元素使用ng模型,则不要使用独立的作用域看到了吗,我可以用孤立范围的ng模型吗?为什么格式化程序不使用独立的作用域?
  • 如果该指令不修改任何范围/模型属性,则不要创建新的范围
  • 如果指令封装了一组DOM元素(文档称为"复杂的DOM结构"),并且该指令将用作元素,或者在同一元素上没有其他指令,那么隔离作用域似乎工作得很好。

我知道,在一个元素上使用带有隔离作用域的指令会强制同一个元素上的所有其他指令使用相同的(一个)隔离作用域,所以当可以使用隔离作用域时,这不会严重限制吗?

我希望来自AngularUI团队的一些人(或编写了许多指令的其他人)能够分享他们的经验。

请不要添加一个简单地说"使用可重用组件的独立作用域"的答案。


多好的问题啊!我很想听听别人怎么说,但这里是我使用的指导原则。好的。

高海拔前提:范围被用作"粘合剂",我们使用它来在父控制器、指令和指令模板之间进行通信。好的。

父范围: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}}",它将再次起作用。因此,如果有一个令人信服的理由在子范围上使用一个独立的范围,但您担心其中的一些限制,那么您应该知道,如果需要的话,您几乎可以解决所有这些限制。好的。

总结好的。

没有新作用域的指令是只读的;它们是完全可信的(即应用程序内部的),并且它们不接触杰克。具有子范围的指令添加功能,但它们不是唯一的功能。最后,隔离范围是针对作为整个目标的指令的;它们是独立的,所以让它们流氓是可以的(也是最"正确的")。好的。

我想把我最初的想法表达出来,但是当我想到更多的事情时,我会更新这个。但是,天哪-这是一个很长的答案…好的。

附:完全是相切的,但既然我们在讨论范围,我更喜欢说"原型",而其他人更喜欢"原型",这似乎更准确,但只是从舌头上滚下来一点也不好。-)好的。好啊。


我的个人政策和经验:隔离:私人沙盒

我想创建许多范围方法和变量,这些方法和变量只由我的指令使用,并且用户从未看到或直接访问过。我想白名单什么范围的数据对我可用。我可以使用超越来允许用户在父作用域(不受影响)中重新跳转。我不想让我的变量和方法在被隔离的孩子中被访问。

子:内容的一部分

我想创建用户可以访问的范围方法和变量,但与我的指令上下文之外的周围范围(兄弟和父)无关。我还想让所有的父范围数据透明地滴入。

无:简单的只读指令

我不需要处理范围方法或变量。我可能在做一些与作用域无关的事情(比如显示简单的jquery插件、验证等)。

笔记

  • 你不应该让NGModel或其他事情直接影响你的决定。你可以通过做类似于ng-model=$parent.myValngModel: '='这样的事情来规避奇怪的行为。
  • isolate+exclude将把所有正常行为恢复到兄弟指令,并返回到父范围,因此也不要让这影响您的判断。
  • 不要把作用域搞得一团糟,因为它就像把数据放在DOM下半部分的作用域上,而不是放在0意义上的上半部分。
  • 注意指令优先级(没有具体的例子说明这会如何影响事物)
  • 注入服务或使用控制器与任何范围类型的指令进行通信。您还可以执行require: '^ngModel'来查找父元素。


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

如果您要编写的指令将完全独立地运行,并且您不打算与其他指令共享它,请转到独立的范围。(就像一个组件一样,您只需插入它,而不需要为最终开发人员进行太多的定制)(当您试图编写包含指令的子元素时,它会变得非常棘手)

如果要编写的指令只是进行不需要内部作用域状态或显式作用域更改(大多数是非常简单的事情)的DOM操作,那么就不要使用新的作用域。(如ngShowngMouseHoverngClickngRepeat)

如果要编写的指令需要更改父作用域中的某些元素,但还需要处理一些内部状态,请转到新的子作用域。(如ngController)

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


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

默认(例如未声明或范围:假)

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

作用范围:{}

这就像一个模块,它想要使用的任何东西都需要显式地传入。如果您使用的每个指令都是一个独立的作用域,那么它就相当于让您编写自己的模块的每个JS文件在注入所有依赖项时都有大量开销。

经营范围:儿童

这是全局变量和显式传递之间的中间地带。它类似于javascript的原型链,只是扩展了父范围的副本。如果您创建一个隔离作用域并传入父作用域的每个属性和函数,那么它在功能上等同于这个作用域。

关键是任何指令都可以以任何方式编写。不同的范围声明只是为了帮助您组织。你可以把每件事都变成一个模块,或者你只需要使用所有的全局变量并非常小心。为了便于维护,最好将逻辑模块化为逻辑连贯的部分,在开阔的草地和封闭的监狱之间保持平衡。我认为这很棘手的原因是,当人们了解这一点时,他们认为自己正在学习指令的工作原理,但实际上他们正在学习代码/逻辑组织。

另一件帮助我了解指令如何工作的事情是学习nginclude。nginclude帮助您包含HTML部分。当我第一次开始使用指令时,我发现您可以使用它的模板选项来减少代码,但我并没有真正附加任何逻辑。

当然,在Angular的指令和AngularUI团队的工作之间,我还没有创建自己的指令来做任何实质性的工作,所以我对此的看法可能是完全错误的。


我同意乌木。理论上,孤立的范围听起来很棒并且"可移植",但是在构建我的应用程序以包含非琐碎的功能时,我遇到了需要合并几个指令(一些嵌套在其他指令中或向它们添加属性),以便完全用我自己的HTML编写,这是特定于域的语言的目的。

最后,在一个指令的每次dom调用上(在隔离作用域中是必需的),用多个属性将每个全局值或共享值传递给链是很奇怪的。在DOM中重复编写所有这些内容看起来很愚蠢,即使这些都是共享对象,也会觉得效率低下。它还不必要地使指令声明复杂化。使用$parent"达到"并从指令html中获取值的解决方案看起来是非常糟糕的形式。

我也最终改变了我的应用程序,使其大部分具有子范围的指令,只有很少的隔离——只有那些不需要从父级访问任何东西的指令,而不需要通过简单的、非重复的属性来传递它们。

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

-D