0%

angular-view-encapsulation

Introduction

Angular中有三种样式封装方式,分别是:Emulated, ShadowDom, and None

  • ViewEncapsulation.Emulated (Default value)
  • ViewEncapsulation.ShadowDom
  • ViewEncapsulation.None

可以用过Component中的encapsulation属性来指定封装方式,如果不指定的话,默认是Emulated

1
2
3
4
5
6
7
8
@Component({
selector: 'app-product',
templateUrl: './product.component.html',
styleUrls: ['./product.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom, // <--- here
})
export class ProductComponent {
}

下面我们分别讲解三种封装方式。

Emulated

这是默认的封装方式,如果你没有在@Component中显式指定encapsulation的话,那么使用的就是该方式。在该方式下,Angular会生成一个随机字符串做为组件的属性,然后将样式应用到这个属性上。
这种封装方式是模拟Shadow DOM的方式,Angular会更改组件的CSS选择器,以便样式只应用于组件的视图。这意味着组件的样式不会影响应用程序的其他部分。

以下面的Product组件为例,product-home.component.scss这个样式文件里面的样式只会影响ProductHomeComponent这个组件对应的视图.

1
2
3
4
5
6
7
8
@Component({
selector: 'app-product-home',
templateUrl: './product-home.component.html',
styleUrl: './product-home.component.less',
})
export class ProductHomeComponent {
protected readonly name = 'Philip';
}

运行app后,我们可以在浏览器中inspect生成后的页面,可以看到app-product-home元素有一个唯一的属性_nghost-ng-c3352351300:

1
2
3
<app-product-home _nghost-ng-c3352351300>
<!-- ... -->
</app-product-home>

再看一下实际的例子,假如我们有如下html template, ProductHomeComponent中引入了ProductDetailComponent

1
2
3
<!-- product-home.component.html -->
<p>product-home works!</p>
<app-product-detail></app-product-detail>

那么生成的HTML如下:
emulated

可以看到,Angular为生成的Dom节点都添加了额外的属性,这些属性大概分为两类:

  • _nghost-xxxx - 用来标记组件的host element,比如上图中第一个红框内
  • _ngcontent-xxxx - 用来标记组件的子元素,比如上图中app-product-home内的p元素,它就是一个纯内容,所以标记是_ngcontent-xxxx
  • 有些元素既是host, 又是content,比如上图的第二个红框。因为ProductDetailComponent既是ProductHomeComponent的子元素,而它本身又是一个组件。所以它同时拥有_nghost-xxxx_ngcontent-xxxx两个属性。

如果我们给上面的p标签添加一个背景色的话,那么观察生成后的css样式,你会发现,样式文件里面也添加了额外的属性,这样就确保了样式的唯一性。

观察下图中左侧p标签的属性,和右侧css文件中product-container的属性,可以看到他们是一一对应的。
emulated-css

Emulated模式生成的样式最终会插入到html文件的head标签中。(可以运行Angular app,然后鼠标右键-inspect查看对应的html文件代码)

ShadowDom

这种方式使用浏览器内置的Shadow DOM API来封装组件的样式,该模式下,组件被放到一个ShadowRoot之下,这个Shadow Root相当于组件的host element, 这意味着组件的样式不会影响应用程序的其他部分。

该模式生成的样式会直接放到shadow-root中。

1
2
3
4
5
6
7
8
9
@Component({
selector: 'app-product-home',
templateUrl: './product-home.component.html',
styleUrl: './product-home.component.less',
encapsulation: ViewEncapsulation.ShadowDom,
})
export class ProductHomeComponent {
protected readonly name = 'Philip';
}

注意观察生成的html文件,app-product-home元素会有一个shadow-root:

1
2
3
4
<app-product-home>
#shadow-root (open)
<!-- ... -->
</app-product-home>

None

这种模式不进行任何封装,相当于裸奔,其效果等于直接在html中引入样式文件。这种模式谨慎使用,因为会有样式污染。

该模式生成的样式会直接放到html文件的head中。

1
2
3
4
5
6
7
8
9
@Component({
selector: 'app-product-home',
templateUrl: './product-home.component.html',
styleUrl: './product-home.component.less',
encapsulation: ViewEncapsulation.None,
})
export class ProductHomeComponent {
protected readonly name = 'Philip';
}

The generated HTML will not have any unique attributes or shadow root:

1
2
3
<app-product-home>
<!-- ... -->
</app-product-home>

注意:ViewEncapsulation.None ViewEncapsulation.Emulated中的样式同时会插入到ViewEncapsulation.ShadowDom的组件中。

References

  1. https://v17.angular.io/guide/view-encapsulation - 这里面的例子要仔细研究一下,有很多细节。