在AngularJS中编写指令时,如何决定是否不需要新的范围,新的子范围或新的隔离范围?
我正在寻找一些指导方针,可以帮助您确定在编写新指令时使用哪种types的范围。 理想情况下,我想要一个类似于stream程图的东西,通过一系列的问题,并popup正确的答案 – 没有新的范围,新的子范围或新的隔离范围 – 但这可能要求太多。 这是我目前微不足道的准则:
- 如果要使用该指令的元素使用ng-model,则不要使用隔离范围
请参阅我是否可以使用具有隔离范围的ng模型? 和
为什么格式化程序不能使用独立的作用域? - 如果指令不修改任何作用域/模型属性,则不要创build新的作用域
- 如果指令封装了一组DOM元素( 文档中提到“一个复杂的DOM结构”),并且该指令将作为一个元素使用,或者在同一元素上没有其他指令,那么隔离范围似乎工作正常。
我知道,在一个元素上使用隔离作用域的指令会强制在同一个元素上的所有其他指令使用相同(一个)隔离作用域,所以当隔离作用域可以被使用时,这不会严重限制吗?
我希望有一些来自Angular-UI团队(或者其他写了很多指令的团队)可以分享他们的经验。
请不要添加一个简单的说“使用隔离作用域作为可重用组件”的答案。
真是个好问题! 我很想听听其他人有什么要说的,但这里是我使用的指南。
高空前提:范围用作我们用来在父控制器,指令和指令模板之间进行通信的“胶水”。
父范围: scope: false
,所以根本没有新的范围
我不经常使用它,但正如@MarkRajcok所说,如果指令不访问任何范围variables(显然没有设置任何!),那么就我而言这就好了。 这对于仅在父指令的上下文中使用的子指令也是有用的(尽pipe总是有例外情况)并且没有模板。 基本上任何有模板的东西都不属于共享范围,因为你本质上暴露了访问和操作的范围(但是我相信这个规则有例外)。
作为一个例子,我最近创build了一个指令,使用我正在编写的SVG库绘制(静态)vectorgraphics。 它$observe
两个属性( width
和height
)并在计算中使用这些属性,但它既不设置也不读取任何范围variables,也没有模板。 这是一个很好的用例,不用创build另一个范围。 我们不需要一个,那么为什么要麻烦?
但是,在另一个SVG指令中,我需要一组数据来使用,另外还需要存储一些状态。 在这种情况下,使用父范围将是不负责任的(一般而言)。 所以相反…
子范围: scope: true
具有子范围的指令是上下文感知的,并且旨在与当前范围交互。
显然,隔离范围的一个关键优点是用户可以自由地使用他们想要的任何属性的插值; 例如在一个隔离作用域的指令上使用class="item-type-{{item.type}}"
在默认情况下不起作用,但对于有子作用域的作品可以正常工作,因为任何插入的内容都可以默认在父范围。 而且,指令本身可以在自己的范围内安全地评估属性和expression式,而不用担心父母的污染或损害。
例如,工具提示就是刚刚添加的东西; 隔离作用域将不起作用(默认情况下,见下文),因为我们希望在这里使用其他指令或插入的属性。 工具提示只是一个增强。 但是工具提示还需要在一个子指令和/或模板上设置一些东西,显然要pipe理它自己的状态,所以使用父范围确实是相当糟糕的。 我们要么污染它,要么破坏它,也不是布埃诺。
我发现自己使用子范围比孤立或父范围更经常。
隔离范围: scope: {}
这是用于可重用的组件。 🙂
但是,我认为,“可重用组件”是“独立组件”。 目的是为了特定目的而使用它们,所以将它们与其他指令相结合,或者将其他插值属性添加到DOM节点本身是没有意义的。
更具体地说,通过在父范围上下文中评估的指定属性来提供这个独立function所需的任何东西; 它们是单向string('@'),单向expression式('&')或双向variables绑定('=')。
在自包含的组件上,由于它本身存在,因此需要在其上应用其他指令或属性是没有意义的。 它的风格是由自己的模板(如有必要)pipe理,并可以有适当的内容transcluded(如有必要)。 它是独立的,所以我们把它放在一个孤立的范围内,也可以说:“不要惹这个,我通过这几个属性给你定义的API。
最好的做法是从指令链接和控制器function中排除尽可能多的基于模板的内容。 这提供了另一个“类似API”的configuration点:指令的用户可以简单地replace模板! 所有function都保持不变,其内部的API从来没有被触及过,但是我们可以尽我们所需地将样式和DOM实现混淆在一起。 ui / bootstrap是如何做到这一点的一个很好的例子,因为彼得和帕维尔真棒。
隔离作用域也适用于跨越式应用。 采取标签; 它们不仅是整个function,而且其内部的任何内容都可以在父范围内自由评估,而让标签(和窗格)能够做任何他们想要的。 这些选项卡显然有自己的状态 ,它属于范围(与模板交互),但该状态与使用它的上下文无关 – 这完全是使tab选项指向tab选项的内部。 此外,使用任何其他指令与选项卡没有太大的意义。 他们是标签 – 我们已经有了这个function!
用更多的function环绕它,或者传递更多的function,但是指令是已经有的。
所有这一切,我应该注意到,有一些孤立作用域的局限性(即特征),正如@ProLoser在他的回答中暗示的。 例如,在子作用域部分中,我提到了在使用隔离作用域时非插入式属性的插值(默认情况下)。 但是,例如,用户可以简单地使用class="item-type-{{$parent.item.type}}"
,它将再次运行。 因此,如果有一个令人信服的理由来使用隔离范围而不是子范围,但是您担心其中的一些限制,请确保您可以根据需要解决所有这些限制。
概要
没有新范围的指令是只读的; 他们完全信任(即内部的应用程序),他们不接触插孔。 具有子范围的指令添加了function,但它们不是唯一的function。 最后,隔离范围是针对整个目标的指令; 他们是独立的,所以可以(最“正确”)让他们stream氓。
我想让我的想法出来,但是当我想到更多的东西时,我会更新它。 但圣洁的废话 – 这是一个很长的答案…
PS:完全切线,但是因为我们在谈论示波器,所以我倾向于说“原型”,而另一些人则更喜欢“原型”,这似乎更准确,但是一言不发。 🙂
我个人的政策和经验:
隔离: 一个私人沙箱
我想创build一个只被我的指令使用的范围方法和variables,而且从来不会被用户看到或直接访问。 我想将我的范围数据列入白名单。 我可以使用transclusion来允许用户跳回到父范围(不受影响) 。 我不希望我的variables和方法在transcluded儿童中可访问。
孩子: 内容的一部分
我想创build可以被用户访问的范围方法和variables,但是与我的指令的上下文之外的范围(兄弟和父母)无关。 我也想让所有的父范围数据透明地滴下来。
无: 简单的只读指令
我并不需要混淆范围方法或variables。 我可能正在做一些与范围无关的事情(比如显示简单的jQuery插件,validation等)。
笔记
- 你不应该让ngModel或其他东西直接影响你的决定。 你可以通过
ng-model=$parent.myVal
(child)或者ngModel: '='
(isolate)来避开奇怪的行为。 - 隔离 + transclude将恢复到兄弟指令的所有正常行为,并返回到父范围,所以不要让这影响你的判断。
- 不要把这个范围搞乱,因为这就像把数据放在DOM的下半部分的范围内,而不是上半部分,这使得0的意义。
- 注意指令的优先级(没有具体的例子说明这可能会影响事物)
- 注入服务或使用控制器通过指令与任何作用域types进行通信。 你也可以这样做
require: '^ngModel'
查找父元素。
写了很多指令后,我决定使用较less的isolated
范围。 尽pipe它很酷,而且封装了数据,并且确保不会将数据泄漏到父范围,但这严重限制了您可以一起使用的指令数量。 所以,
如果您要编写的指令将完全依靠自己的行为,而您不打算将其与其他指令共享,则可以使用隔离范围 。 (就像一个你可以插入的组件,对于最终的开发人员来说没有太多的自定义)(当你尝试编写有指令的子元素时,它会变得非常复杂)
如果你要编写的指令只是做一些不需要内部状态范围的操作,或者是明确的范围变更(通常是非常简单的事情); 没有新的范围 。 (如ngShow
, ngMouseHover
, ngClick
, ngRepeat
)
如果您要编写的指令需要更改父范围中的某些元素,而且还需要处理某些内部状态,请去新的子范围 。 (如ngController
)
请务必查看指令的源代码: https : //github.com/angular/angular.js/tree/master/src/ng/directive
这对如何思考它们有很大的帮助
只是想我会增加我目前的理解,以及它如何与其他JS概念。
默认(例如未声明或范围:false)
这在哲学上等同于使用全局variables。 您的指令可以访问父控制器中的所有内容,但也会影响到它们并同时受到影响。
范围:{}
这就像一个模块,任何想要使用的东西都需要明确地传入。 如果你使用的EVERY指令是一个隔离作用域,它可以等效于使得每个JS文件都写入自己的模块,并在注入所有依赖的时候带来很多开销。
范围:孩子
这是全局variables和显式直通之间的中间地带。 它与JavaScript的原型链类似,只是扩展了父范围的副本。 如果您创build隔离范围并传递父范围的每个属性和function,则在function上等同于此。
关键是任何指令都可以用任何方式编写。 不同的范围声明只是为了帮助您组织。 你可以把所有东西都做成一个模块,或者你可以使用所有的全局variables,并且要非常小心。 为了便于维护,尽pipe最好将逻辑模块化为逻辑连贯的部分。在开放的草地和封闭的监狱之间有一个平衡点。 这个问题的原因很棘手,我相信当人们了解这个时,他们认为他们正在学习指令如何工作,但实际上他们正在学习代码/逻辑组织。
另一件帮助我弄清楚指令是如何工作的是学习ngInclude。 ngInclude可以帮助你包含html partials。 当我第一次使用指令时,我发现你可以使用它的模板选项来减less你的代码,但是我并没有真正附加任何逻辑。
当然,在angular的指令和angular-ui团队的工作之间,我还没有创build自己的指令来做任何实质性的事情,所以我的观点可能是完全错误的。
我同意Umur。 理论上,隔离的范围听起来很棒,而且“便携”,但是在构build我的应用程序以涉及非平凡的function时,我碰到需要合并几个指令(一些嵌套在其他指令或添加属性给他们),以便完全写在我的自己的HTML,这是一个领域特定语言的目的。
最后,在指令的每个DOM调用(如隔离范围所需的)上传递每个全局或共享值的链时,都会出现奇怪的情况。 在DOM中反复写入所有内容并且效率低下,即使它们是共享对象也是愚蠢的。 这也不必要地使指令声明复杂化。 使用$ parent来“伸手可及”并从指令HTML中获取值的解决方法看起来像非常糟糕的forms。
我也改变了我的应用程序大部分的子范围指令只有很less的隔离区 – 只有那些不需要从父类访问任何东西,而不是通过简单的,不重复的属性可以传递的东西。
梦想着域特定语言已经有好几十年了,我很高兴AngularJS提供了这个select,我知道,随着越来越多的开发者在这个领域工作,我们将看到一些非常酷的应用程序,对他们的架构师来说也很容易编写,扩展和debugging。
– D