这里会涉及到 Angular
的 ViewEncapsulation
,即控制视图的封装模式,主要分为三种,原生(Native
)、仿真(Emulated
)和无(None
)三种
Native
模式,完全隔离,外面的样式无法影响组件,组件里面的样式也无法影响外面Emulated
模式(默认值),全局样式可以影响组件,但组件样式无法影响外层None
意味着完全消除隔离特性,全局样式可以影响组件,组件样式也可以影响外层- 这种情况下
Angular
不使用视图封装,会把CSS
添加到全局样式中,而不会应用之前那些作用域规则、隔离和保护等 - 从本质上来说,这跟把组件的样式直接放进
HTML
是一样的
- 这种情况下
术语上来说就是
ViewEncapsulation
允许设置三个可选的值
ViewEncapsulation.Emulated
- 没有
Shadow DOM
,但是通过Angular
提供的样式包装机制来封装组件,使得组件的样式不受外部影响,这是Angular
的默认设置 - 虽然样式仍然是应用到整个
document
,但Angular
会为所在的类创建了一个[_ngcontent-cmy-0]
选择器
- 没有
ViewEncapsulation.Native
- 使用原生的
Shadow DOM
特性,Angular
会把组件按照浏览器支持的Shadow DOM
形式渲染
- 使用原生的
ViewEncapsulation.None
- 即没有
Shadow DOM
,并且也无样式包装,即所有的样式都应用到整个document
,换句话说,组件的样式会受外界影响,可能被覆盖掉
- 即没有
唯一的区别在于 Shadow DOM
,当然其作用是让组件的样式只进不出,换言之即组件内的样式不会影响到外部组件,三者的表现形成如下,假设基本模版为
1 | @Component({ |
三种方式生成的分别为
Native
1 | #shadow-root (open) |
Emulated
1 | <style>h1[_ngcontent-c0] { color: #f50; }</style> |
None
1 | <style>h1 { color: #f50; }</style> |
需要注意的是 Native
和 None
在内容是一样的,但其后者会影响至其他外部组件的 h1
元素
组件样式
组件样式的封装模式取决于我们对 encapsulation
的配置,当然你可以了在 main.ts
时为所有组件统一设定一种行为模式,例如
1 | // 使用 None 模式 |
虽然三种模式都有不同的风格,但对于一个组件而言,如果没有统一使用风格,那么在实际项目中则会让我们很头疼,特别是当项目中同时在使用第三方组件库,情况会更为复杂,比如你在某一个组件当中添加了一个 class
样式
1 | .active-link { |
若组件设定为 None
模式,只要该组件出现过一次,并且在未来所有即使不再使用,那么这个样式也会得到保留,所有的添加了这个 class
的元素均会改变,反之,对于 Shadow
行为,它会为该组件创建一个额外的属性 _ngcontent-c1
来标识(不管是 Native
、Emulated
本质是一样的)所设定的样式仅限于当前组件当中,而 Angular
中即采用 :host
来表示组件自身,所以前面的 CSS
样式可以调整为
1 | :host .active-link { |
然而我们会发现,对于第三方组件组件而言,.active-link
是其组件内部某个 HTML
元素的 class
而已,且它有自己的一套组件封装规则,但我们生成的 CSS
中包括了一个奇怪的字符 [_ngcontent-c1]
,最终导致该组件样式无法改变内嵌的第三方组件内容的样式,在这种情况下,Angular
提供了一种对未来工具更好兼容性的命令 ::ng-deep
来强制样式允许侵入子组件
1 | :host ::ng-deep .active-link { |
最终的结果就是这个样式只会在这个组件内部当中有效