在AngularJS中编写指令时,我如何决定不需要新的作用域、新的子作用域还是新的隔离作用域?

我正在寻找一些指导方针,可以用来帮助确定在编写新指令时使用哪种类型的作用域。理想情况下,我想要一些类似于流程图的东西,带我走过一堆问题,然后弹出正确的答案。没有新的新作用域,新的子作用域,或新的隔离作用域–但这可能要求太多了。以下是我目前的一套微不足道的指导方针:

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

我希望来自Angular-UI团队的一些人(或者其他写过很多指令的人)能分享他们的经验。

请不要添加一个简单地说“为可重用组件使用隔离范围”的答案。

42532 次浏览

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

如果你要编写的指令将单独表现为完全,并且你不打算与其他指令共享它,那么就选择范围隔离。(就像一个组件,你可以把它插进去,对于最终的开发人员来说没有太多的定制)(当你试图编写包含指令的子元素时,它变得非常棘手)

如果你要写的指令是只是做dom操作,不需要内部的作用域状态,或显式的作用域更改(大多数是非常简单的事情);去没有新的作用域。(如ngShowngMouseHoverngClickngRepeat)

如果你要编写的指令需要改变父作用域中的一些元素,但也需要处理一些内部状态,那么选择新的子范围。(例如ngController)

一定要检查指令的源代码:https://github.com/angular/angular.js/tree/master/src/ng/directive
它极大地帮助你思考它们

多好的问题啊!我听别人说什么,但这里是我使用的指导方针。

高度前提:作用域被用作父控制器、指令和指令模板之间通信的“胶水”。

父范围: scope: false,所以没有新的作用域

我不经常使用这个,但正如@MarkRajcok所说,如果该指令不访问任何作用域变量(显然也不设置任何作用域变量!),那么就我而言,这是很好的。这对于在父指令上下文中使用只有且没有模板的子指令也很有帮助(尽管总是有例外)。基本上,任何带有模板的东西都不属于共享作用域,因为您固有地暴露了该作用域以供访问和操作(但我确信该规则也有例外)。

举个例子,我最近创建了一个指令,使用我正在编写的SVG库绘制(静态)矢量图形。它$observes两个属性(widthheight)并在其计算中使用它们,但它既不设置也不读取任何作用域变量,并且没有模板。这是一个不创建另一个作用域的好用例;我们不需要,那又何苦呢?

但是,在另一个SVG指令中,我需要使用一组数据,另外还必须存储一小部分状态。在这种情况下,使用父作用域是不负责任的(同样,一般来说)。所以…

孩子的范围: scope: true

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

显然,这种方法相对于隔离作用域的一个关键优势是,用户可以自由地对他们想要的任何属性使用插值;例如,在具有独立作用域的指令上使用class="item-type-\{\{item.type}}"默认情况下是无效的,但在具有子作用域的指令上则可以正常工作,因为无论插入的是什么,默认情况下仍然可以在父作用域中找到。此外,指令本身可以安全地在自己的作用域上下文中计算属性和表达式,而不用担心对父函数的污染或损害。

例如,工具提示是刚添加的东西;孤立的作用域是行不通的(默认情况下,请参见下文),因为我们期望在这里使用其他指令或内插的属性。工具提示只是一种增强。但是工具提示还需要在作用域上设置一些东西,以便与子指令和/或模板一起使用,并且显然要管理它自己的状态,因此使用父作用域确实会非常糟糕。我们不是在污染它,就是在破坏它,两者都不好。

我发现自己更多地使用子作用域而不是孤立作用域或父作用域。

隔离范围: scope: {}

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

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

更具体地说,这个独立功能所需的任何东西都是通过在父作用域上下文中计算的指定属性来提供的;它们要么是单向字符串('@'),要么是单向表达式('&'),要么是双向变量绑定('=')。

在自包含组件上,需要对其应用其他指令或属性是没有意义的,因为它本身就存在。它的样式由它自己的模板管理(如果需要),并且可以包含适当的内容(如果需要)。它是独立的,所以我们把它放在一个孤立的范围内,也就是说:“不要搞砸它。我通过这几个属性给你一个定义好的API。”

一个好的最佳实践是将尽可能多的基于模板的东西从指令链接和控制器函数中排除。这提供了另一个“类似api”的配置点:指令的用户可以简单地替换模板!所有的功能都保持不变,其内部API从未被改变,但我们可以随心所欲地改变样式和DOM实现。ui/bootstrap是一个伟大的的例子,说明了如何做到这一点,因为Peter &帕维尔很棒。

隔离作用域也很适合用于透射。标签;它们不仅是整个功能,而且无论内部是什么,都可以在父范围内自由地求值,而让选项卡(和窗格)做任何他们想做的事情。制表符显然有自己的状态,它属于作用域(与模板交互),但这种状态与使用它的上下文无关——它完全是制表符指令成为制表符指令的内部原因。此外,对选项卡使用任何其他指令也没有多大意义。它们是标签——我们已经有了这个功能!

用更多的功能包围它,或者渗透更多的功能,但指令已经是这样了。

综上所述,我应该指出的是,正如@ProLoser在他的回答中暗示的那样,有一些方法可以绕过孤立作用域的一些限制(即功能)。例如,在子作用域部分,我提到了在使用隔离作用域时(默认情况下)对非指令属性的破坏进行插值。但是用户可以,例如,简单地使用class="item-type-\{\{$parent.item.type}}",它将再次工作。因此,如果有令人信服的理由使用孤立作用域而不是子作用域,但您担心其中的一些限制,请知道,如果需要,您几乎可以绕过所有这些限制。

总结

没有新的作用域的指令是只读的;他们是完全可信的(即在应用程序内部),他们不接触任何东西。带有子作用域添加功能的指令,但它们不是唯一的功能。最后,隔离作用域用于作为整个目标的指令;它们是独立的,所以让它们失控是可以的(也是最“正确的”)。

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


PS:完全离题,但既然我们在谈论范围,我更喜欢说“原型”,而其他人更喜欢说“原型”,这似乎更准确,但只是从舌头上滑下来,一点也不好。: -)

我的个人政策和经验:

孤立:私人沙盒

我想创建大量的作用域方法和变量,仅由我的指令使用,永远不会被用户看到或直接访问。我想列出对我可用的范围数据。我可以使用transclusion允许用户跳转回父范围(不受影响)。我做希望我的变量和方法在transclexcluded child中可访问。

孩子:内容的分段

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

没有:简单的只读指令

我真的不需要干扰作用域方法或变量。我可能正在做一些与作用域无关的事情(比如显示简单的jQuery插件、验证等)。

笔记

  • 你不应该让ngModel或其他东西直接影响你的决定。可以通过执行ng-model=$parent.myVal (child)或ngModel: '=' (isolate)之类的操作来避免奇怪的行为。
  • 隔离 + transclude将恢复所有正常行为到兄弟指令,并返回到父范围,所以不要让这影响你的判断。
  • 不要混淆没有一个上的作用域,因为这就像将数据放在DOM下半部分的作用域上,而不是上半部分,这是没有意义的。
  • 注意指令的优先级(不要给出具体的例子说明这会如何影响事情)
  • 注入服务或使用控制器在任何作用域类型的指令之间通信。你也可以用require: '^ngModel'来查找父元素。

我同意乌穆尔的看法。从理论上讲,孤立的作用域听起来很棒,而且“可移植”,但在构建我的应用程序时,我发现需要合并几个指令(一些指令嵌套在其他指令中,或向它们添加属性),以便完全用我自己的HTML编写,这就是领域特定语言的目的。

最后,必须在指令的每次DOM调用上传递具有多个属性的每个全局值或共享值(正如隔离作用域所要求的那样),这太奇怪了。在DOM中重复编写所有这些内容看起来很愚蠢,而且感觉效率很低,即使这些是共享对象。它还不必要地使指令声明复杂化。使用$parent来“达到”并从指令HTML中获取值的解决方法看起来非常糟糕。

我也改变了我的应用程序,大部分都是子作用域指令,只有那些不需要从父作用域访问任何东西的指令,除了可以通过简单的、非重复的属性传递的指令。

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

——D

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

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

这在哲学上等同于使用全局变量。你的指令可以访问父控制器中的所有东西,但同时也会影响它们和被影响。

范围:{}

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

范围:孩子

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


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

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

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