在 Angular 中,组件的样式可以封装在组件的宿主元素中,这样它们就不会影响应用程序的其余部分。
Component
的装饰器提供了 encapsulation
选项,可用来控制如何基于每个组件应用视图封装。
从以下模式中选择:
ViewEncapsulation.ShadowDom
,Angular 使用浏览器内置的 Shadow DOM API将组件的视图包含在 ShadowRoot(用作组件的宿主元素)中,并以隔离的方式应用所提供的样式。 ViewEncapsulation.Emulated
,Angular 会修改组件的 CSS 选择器,使它们只应用于组件的视图,不影响应用程序中的其他元素(模拟 Shadow DOM 行为)。ViewEncapsulation.None
,Angular 不应用任何形式的视图封装,这意味着为组件指定的任何样式实际上都是全局应用的,并且可以影响应用程序中存在的任何 HTML 元素。这种模式本质上与将样式包含在 HTML 本身中是一样的。ViewEncapsulation.ShadowDom
仅适用于内置支持 shadow DOM 的浏览器(请参阅 Can I use - Shadow DOM v1)。并非所有浏览器都支持它,这就是为什么 ViewEncapsulation.Emulated
是推荐和默认模式的原因。
使用模拟视图封装时,Angular 会预处理所有组件的样式,以便它们仅应用于组件的视图。
在正运行的 Angular 应用程序的 DOM 中,使用模拟视图封装模式的组件所在的元素附加了一些额外的属性:
<hero-details _nghost-pmm-5>
<h2 _ngcontent-pmm-5>Mister Fantastic</h2>
<hero-team _ngcontent-pmm-5 _nghost-pmm-6>
<h3 _ngcontent-pmm-6>Team</h3>
</hero-team>
</hero-detail>
有两种这样的属性:
_nghost
属性被添加到包裹组件视图的元素中,这将是本机 Shadow DOM 封装中的 ShadowRoots。组件的宿主元素通常就是这种情况。 _ngcontent
属性被添加到组件视图中的子元素上,这些属性用于将元素与其各自模拟的 ShadowRoots(具有匹配 _nghost
属性的宿主元素)相匹配。这些属性的确切值是 Angular 的私有实现细节。它们是自动生成的,你不应在应用程序代码中引用它们。
它们以生成的组件样式为目标,这些样式会被注入到 DOM 的 <head>
部分:
[_nghost-pmm-5] {
display: block;
border: 1px solid black;
}
h3[_ngcontent-pmm-6] {
background-color: white;
border: 1px solid #777;
}
这些样式经过后期处理,以便每个 CSS 选择器都使用适当的 _nghost
或 _ngcontent
属性进行扩充。这些修改后的选择器可以确保样式以隔离和有针对性的方式应用于组件的视图。
如前所述,你可以在组件的装饰器中针对每个组件指定封装模式,这意味着在你的应用程序中,不同的组件可以使用不同的封装策略。
尽管可能,但不建议这样做。如果真的需要,你应该知道使用不同封装模式的组件的样式会如何彼此交互:
ViewEncapsulation.Emulated
的组件样式会添加到文档的 <head>
中,使它们在整个应用程序中可用,但它们的选择器只会影响它们各自组件模板中的元素。 ViewEncapsulation.None
的组件样式会添加到文档的 <head>
中,使它们在整个应用程序中可用,因此是完全全局的,会影响文档中的任何匹配元素。 ViewEncapsulation.ShadowDom
的组件样式仅添加到 shadow DOM 宿主中,确保它们仅影响各自组件视图中的元素。ViewEncapsulation.Emulated
和 ViewEncapsulation.None
组件的样式也会添加到每个 ViewEncapsulation.ShadowDom
组件的 shadow DOM 宿主中。
这意味着带有 ViewEncapsulation.None
的组件的样式将影响 shadow DOM 中的匹配元素。
这种方法乍一看似乎有违直觉,但如果没有它,带有 ViewEncapsulation.None
的组件将在使用 ViewEncapsulation.ShadowDom
的组件内呈现不同的效果,因为其样式将不可用。
本节展示了具有不同 ViewEncapsulation
的组件的样式如何交互的示例。
请参阅 现场演练以自己尝试这些组件。
第一个示例显示了一个具有 ViewEncapsulation.None
的组件。此组件将其模板元素着色为红色。
@Component({
selector: 'app-no-encapsulation',
template: `
<h2>None</h2>
<div class="none-message">No encapsulation</div>
`,
styles: ['h2, .none-message { color: red; }'],
encapsulation: ViewEncapsulation.None,
})
export class NoEncapsulationComponent { }
Angular 将此组件的样式作为全局样式添加到文档的 <head>
中。
如前所述,Angular 还会将这些样式添加到所有 shadow DOM 宿主。因此,样式在整个应用程序中都可用。
第二个示例显示了一个具有 ViewEncapsulation.Emulated
的组件。此组件将其模板元素着色为绿色。
@Component({
selector: 'app-emulated-encapsulation',
template: `
<h2>Emulated</h2>
<div class="emulated-message">Emulated encapsulation</div>
<app-no-encapsulation></app-no-encapsulation>
`,
styles: ['h2, .emulated-message { color: green; }'],
encapsulation: ViewEncapsulation.Emulated,
})
export class EmulatedEncapsulationComponent { }
与 ViewEncapsulation.None
类似,Angular 会将此组件的样式添加到文档的 <head>
中,但它们是带有“作用域”的样式。
因此,只有直接在该组件模板中的元素才会匹配其样式。由于来自 EmulatedEncapsulationComponent
的样式是非常特化的,因此它们会覆盖来自 NoEncapsulationComponent
的全局样式。
在此示例中,EmulatedEncapsulationComponent
包含着 NoEncapsulationComponent
, 但 NoEncapsulationComponent
仍然如预期般生效了,因为 EmulatedEncapsulationComponent
的“范围化”样式与其模板中的元素并不匹配。
第三个示例显示了一个具有 ViewEncapsulation.ShadowDom
的组件。此组件会将其模板元素着色为蓝色。
@Component({
selector: 'app-shadow-dom-encapsulation',
template: `
<h2>ShadowDom</h2>
<div class="shadow-message">Shadow DOM encapsulation</div>
<app-emulated-encapsulation></app-emulated-encapsulation>
<app-no-encapsulation></app-no-encapsulation>
`,
styles: ['h2, .shadow-message { color: blue; }'],
encapsulation: ViewEncapsulation.ShadowDom,
})
export class ShadowDomEncapsulationComponent { }
Angular 仅将此组件的样式添加到 shadow DOM 宿主,因此它们在 shadow DOM 之外是不可见的。
请注意,Angular 还将 NoEncapsulationComponent
和 ViewEncapsulationComponent
的全局样式添加到了 shadow DOM 宿主中,因此这些样式仍然可用于该组件的模板中的元素。
在这个例子中, ShadowDomEncapsulationComponent
包含一个 NoEncapsulationComponent
和 ViewEncapsulationComponent
。
ShadowDomEncapsulationComponent
组件添加的样式在该组件的整个 shadow DOM 中都可用,在 NoEncapsulationComponent
和 ViewEncapsulationComponent
中也是如此。
EmulatedEncapsulationComponent
具有特化的“范围化”样式,因此该组件模板的样式不受影响。
但是由于 ShadowDomEncapsulationComponent
中的样式是在全局样式之后添加到 Shadow Host 中的,因此 h2
样式会覆盖 NoEncapsulationComponent
中的样式。结果是 NoEncapsulationComponent
中的 <h2>
元素被着色为蓝色而不是红色,这可能不是组件作者的本意。