这里会涉及到 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 { |
最终的结果就是这个样式只会在这个组件内部当中有效