我正在寻找一些指导方针,可以用来帮助确定在编写新指令时使用哪种类型的范围。理想情况下,我想要类似于流程图的东西,它引导我完成一堆问题并弹出正确的答案——没有新的新范围、新的子范围或新的隔离范围——但这可能要求太多了。这是我目前的一套微不足道的指导方针:
如果将使用指令的元素使用 ng-model,请不要使用隔离范围请参阅我可以将 ng-model 与隔离范围一起使用吗?以及为什么格式化程序不适用于隔离范围?
如果指令不修改任何范围/模型属性,请不要创建新范围
如果指令封装了一组 DOM 元素(文档说“复杂的 DOM 结构”)并且指令将用作元素,或者在同一元素上没有其他指令,则隔离范围似乎工作得很好。
我知道在元素上使用具有隔离范围的指令会强制同一元素上的所有其他指令使用相同的(一个)隔离范围,所以这不会严重限制何时可以使用隔离范围吗?
我希望 Angular-UI 团队中的一些人(或其他编写过许多指令的人)可以分享他们的经验。
请不要添加简单地说“为可重用组件使用隔离范围”的答案。
scope: true
将使用 $scope.new()
自动创建一个子范围。
scope: false
(默认值,没有新范围)、scope: true
(继承原型的新范围)和 scope: { ... }
(新隔离范围)。
多么棒的问题啊!我很想听听其他人的意见,但这里是我使用的指导方针。
高空前提:作用域作为我们用来在父控制器、指令和指令模板之间进行通信的“胶水”。
父范围: scope: false
,所以根本没有新范围
我不经常使用它,但正如@MarkRajcok 所说,如果指令不访问任何范围变量(并且显然没有设置任何变量!)那么就我而言这很好。这对于仅在父指令的上下文中使用并且没有模板的子指令也很有帮助(尽管总是有例外)。基本上,任何带有模板的东西都不属于共享范围,因为您天生就暴露了该范围以进行访问和操作(但我确信这条规则有例外)。
例如,我最近创建了一个指令,该指令使用我正在编写的 SVG 库绘制(静态)矢量图形。它$observe
有两个属性(width
和 height
)并在其计算中使用这些属性,但它既不设置也不读取任何范围变量,也没有模板。这是不创建另一个范围的好用例;我们不需要,那何必呢?
但是,在另一个 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:完全切线,但由于我们在谈论范围,我更喜欢说“原型”,而其他人更喜欢“原型”,这似乎更准确,但只是不太好说。 :-)
我的个人政策和经验:
隔离:私人沙箱
我想创建很多范围方法和变量,这些方法和变量仅由我的指令使用,用户永远不会看到或直接访问。我想将我可以使用的范围数据列入白名单。我可以使用嵌入来允许用户跳回父范围(不受影响)。我不希望我的变量和方法在嵌入的孩子中可以访问。
子项:内容的一个小节
我想创建用户可以访问的范围方法和变量,但与我的指令上下文之外的周围范围(兄弟姐妹和父母)无关。我还想让所有父范围数据透明地向下流动。
无:简单的只读指令
我真的不需要弄乱范围方法或变量。我可能正在做一些与范围无关的事情(例如显示简单的 jQuery 插件、验证等)。
笔记
你不应该让 ngModel 或其他东西直接影响你的决定。你可以通过 ng-model=$parent.myVal (child) 或 ngModel: '=' (isolate) 来规避奇怪的行为。
Isolate + transclude 会将所有正常行为恢复到兄弟指令并返回父范围,因此也不要让这影响您的判断。
不要在 none 上混淆范围,因为这就像将数据放在 DOM 的下半部分而不是上半部分的范围上,这使得 0 有意义。
注意指令优先级(没有具体的例子说明这会如何影响事情)
注入服务或使用控制器在具有任何作用域类型的指令之间进行通信。你也可以 require: '^ngModel' 来查看父元素。
在编写了很多指令之后,我决定使用较少的 isolated
范围。尽管它很酷并且您封装了数据并确保不会将数据泄漏到父范围,但它严重限制了您可以一起使用的指令数量。所以,
如果您要编写的指令将完全独立运行并且您不打算与其他指令共享它,请选择隔离范围。 (就像一个组件,您可以将其插入,最终开发人员没有太多自定义)(当您尝试编写其中包含指令的子元素时,它会变得非常棘手)
如果您要编写的指令只是进行 dom 操作,它不需要范围的内部状态或显式范围更改(大多数是非常简单的事情); 没有新的范围。 (例如 ngShow
、ngMouseHover
、ngClick
、ngRepeat
)
如果您要编写的指令需要更改父范围内的某些元素,但还需要处理一些内部状态,请选择 new child scope。 (例如 ngController
)
请务必查看指令的源代码:https://github.com/angular/angular.js/tree/master/src/ng/directive
这对如何考虑它们有很大帮助
require
,因此保持指令仍然解耦。那么它是如何限制可能性的呢?它甚至使指令更加具体(所以声明你所依赖的)。所以我只留下一条规则:如果你的指令有状态或需要来自使用它的范围的一些数据 - 使用隔离范围。否则不要使用范围。关于“子作用域”——我也写了很多指令,从来不需要这个功能。如果“需要更改父范围内的某些元素” - 使用绑定。
$parent
hack)。所以实际上指令的“子作用域”看起来应该在后面使用——比如 ngRepeat
为每个要重复的项目创建新的子作用域(但它也使用 scope.$new();
而不是 scope: true
创建它。
ngClick
等)我同意要求创建一种解耦,但您仍然需要了解父指令。除非它像一个组件,否则我反对隔离。指令(至少,大多数指令)是高度可重用的,而隔离打破了这一点。
只是想我会添加我目前的理解以及它与其他 JS 概念的关系。
默认(例如未声明或范围:false)
这在哲学上等同于使用全局变量。您的指令可以访问父控制器中的所有内容,但它也会影响它们并同时受到影响。
范围:{}
这就像一个模块,它想要使用的任何东西都需要显式传入。如果您使用的每个指令都是一个隔离作用域,则它可以等效于使每个 JS 文件都编写自己的模块,并且在注入所有依赖项时会产生大量开销。
范围:儿童
这是全局变量和显式传递之间的中间地带。它类似于 javascript 的原型链,只是扩展了父范围的副本。如果您创建一个隔离作用域并传入父作用域的每个属性和功能,它在功能上与此等效。
关键是任何指令都可以用任何方式编写。不同的范围声明只是为了帮助您组织。您可以将所有内容都设为模块,或者您可以只使用所有全局变量并非常小心。为了便于维护,尽管最好将您的逻辑模块化为逻辑连贯的部分。在开放的草地和封闭的监狱之间有一个平衡。我认为这很棘手的原因是,当人们了解这一点时,他们认为他们正在学习指令如何工作,但实际上他们正在学习代码/逻辑组织。
帮助我弄清楚指令如何工作的另一件事是了解 ngInclude。 ngInclude 帮助您包含 html 部分。当我第一次开始使用指令时,我发现你可以使用它的模板选项来减少你的代码,但我并没有真正附加任何逻辑。
当然,在 angular 的指令和 angular-ui 团队的工作之间,我还没有创建自己的指令来做任何实质性的事情,所以我对此的看法可能完全错误。
我同意乌穆尔。从理论上讲,孤立的作用域听起来很棒并且“可移植”,但是在构建我的应用程序以涉及非平凡的功能时,我遇到了需要合并几个指令(一些嵌套在其他指令中或向它们添加属性),以便完全写入我的自己的 HTML,这是域特定语言的目的。
最后,在指令的每个 DOM 调用上必须将具有多个属性的每个全局或共享值沿链向下传递,这太奇怪了(正如隔离范围所要求的那样)。在 DOM 中重复编写所有内容看起来很愚蠢,并且感觉效率低下,即使这些是共享对象。它还不必要地使指令声明复杂化。使用 $parent 来“向上”并从指令 HTML 中获取值的解决方法似乎是非常糟糕的形式。
我也最终将我的应用程序更改为主要具有很少隔离的子范围指令——只有那些不需要从父级访问任何东西的指令,而不是它们可以通过简单、非重复属性传递的指令。
几十年来一直梦想着领域特定语言的梦想,在这样的事情出现之前,我很高兴 AngularJS 提供了这个选项,我知道,随着越来越多的开发人员在这个领域工作,我们将看到一些非常酷的应用程序他们的架构师也很容易编写、扩展和调试。
-- D
不定期副业成功案例分享
ngInclude
。或者将其作为构建的一部分。很多选择!