:host 和 ::ng-deep

:host 和 ::ng-deep

这里会涉及到 AngularViewEncapsulation,即控制视图的封装模式,主要分为三种,原生(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
2
3
4
5
@Component({
template: `<h1>test</h1>`,
styles: [`h1 { color: #f50; }`],
encapsulation: ViewEncapsulation.Native
})

三种方式生成的分别为

Native

1
2
3
4
#shadow-root (open)
<style>h1 { color: #f50; }</style>

<h1>test</h1>

Emulated

1
2
3
<style>h1[_ngcontent-c0] { color: #f50; }</style>

<h1 _ngcontent-c0>test</h1>

None

1
2
3
<style>h1 { color: #f50; }</style>

<h1>test</h1>

需要注意的是 NativeNone 在内容是一样的,但其后者会影响至其他外部组件的 h1 元素

组件样式

组件样式的封装模式取决于我们对 encapsulation 的配置,当然你可以了在 main.ts 时为所有组件统一设定一种行为模式,例如

1
2
3
4
// 使用 None 模式
platformBrowserDynamic().bootstrapModule(AppModule, {
defaultEncapsulation: ViewEncapsulation.None
})

虽然三种模式都有不同的风格,但对于一个组件而言,如果没有统一使用风格,那么在实际项目中则会让我们很头疼,特别是当项目中同时在使用第三方组件库,情况会更为复杂,比如你在某一个组件当中添加了一个 class 样式

1
2
3
.active-link {
color: red;
}

若组件设定为 None 模式,只要该组件出现过一次,并且在未来所有即使不再使用,那么这个样式也会得到保留,所有的添加了这个 class 的元素均会改变,反之,对于 Shadow 行为,它会为该组件创建一个额外的属性 _ngcontent-c1 来标识(不管是 NativeEmulated 本质是一样的)所设定的样式仅限于当前组件当中,而 Angular 中即采用 :host 来表示组件自身,所以前面的 CSS 样式可以调整为

1
2
3
4
5
6
7
8
9
:host .active-link {
color: red;
}

<!-- 生成的样式为 -->

[_nghost-c1] .active-link[_ngcontent-c1] {
color: red;
}

然而我们会发现,对于第三方组件组件而言,.active-link 是其组件内部某个 HTML 元素的 class 而已,且它有自己的一套组件封装规则,但我们生成的 CSS 中包括了一个奇怪的字符 [_ngcontent-c1],最终导致该组件样式无法改变内嵌的第三方组件内容的样式,在这种情况下,Angular 提供了一种对未来工具更好兼容性的命令 ::ng-deep 来强制样式允许侵入子组件

1
2
3
4
5
6
7
8
9
:host ::ng-deep .active-link {
color: red;
}

<!-- 生成的样式为 -->

[_nghost-c1] .active-link {
color: red;
}

最终的结果就是这个样式只会在这个组件内部当中有效

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×