“多余”的属性
Angular项目中如果在组件中通过 styleUrls 引入样式文件且使用Emulated模式,打开控制台调试DOM时,就会发现这些组件上多出来一些属性。这些属性看似无用,其实是Angular用作样式隔离的,那么这个属性是怎么加上去的呢?为什么能隔离样式呢?
Angular的三种视图封装模式
-
ShadowDom模式,使用浏览器原生的Shadow DOM实现,组件样式的表现为不进不出,全局样式进不来,组件样式出不去
-
Emulated模式,Angular默认的封装模式,模拟Shadow DOM的行为,组件样式表现为只进不出,全局样式能进来,组件样式出不去
-
None模式,不用视图封装,直接把css添加到全局样式中,组件样式表现为能进能出
可见把css样式局限在组件视图中,和Shadow DOM有很大的关系。
shadow Dom 是什么?
一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
Web components 的一个重要属性是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。其中,Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上。
这是MDN上的解释,表示Shadow Dom是隐藏的元素。
写个例子看一看:
有app-list和app-list-item两个组件,引入各自的css文件,且全部使用ShadowDom模式。同时在全局引入一个样式文件global.scss:
app-list
// html
<div class="app-list">
<p class="list-title bg-color">今日代办:</p>
<app-list-item *ngFor="let list of lists" [list]="list"></app-list-item>
</div>
// ts
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom
})
//scss
.app-list{
display: block;
width: 300px;
.list-title{
padding: 10px 20px;
color: #fff;
}
}
.bg-color{
background-color: #66c060;
}
app-list-item
//html
<div class="list-item bg-color">
<input type="checkbox" class="list-item-checkbox" [checked]="list.checked">
<span class="list-item-title">{{list.title}}</span>
</div>
// ts
@Component({
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom
})
//scss
.list-item{
overflow: hidden;
padding: 15px 20px;
border-radius: 4px;
margin-bottom: 5px;
.list-item-checkbox{
margin-right: 20px;
}
}
.bg-color{
background-color: #ccc;
}
global.scss
.bg-color{
background-color: red;
}
展示效果如下:
通过渲染出来的DOM结构可以看到**,组件的宿主app-list内部附加了一个Shadow Dom,以 shadow-root 为根,包裹所有子元素和css,全局css无效,确实做到了内外隔离,不进不出的效果(全局的样式进不来,内部的样式出不去)。**
背景点表示全局样式作用区域
我试着打印了一下:
console.log('开始', document.getElementsByClassName('app-list'))
确实是隐藏的,所以全局样式作用不到。
Emulated模式
Angular通过预处理css代码,且为每个Dom增加了一些额外的属性,来模拟Shadow DOM的行为,与之不同的是,允许全局的样式进入(只进不出)。
看看例子:
代码如上,将 ViewEncapsulation.ShadowDom 换成 ViewEncapsulation.Emulated
app-list上增加了属性_nghost-mgx-c17,以及子元素都增加了属性_ngcontent-mgx-c17app-list-item同时增加了属性_ngcontent-mgx-c17和_nghost-mgx-c16, 内部子元素增加了属性_ngcontent-mgx-c16
也就是说Angular的模拟方式为:如果是ShadowDom模式下的宿主, 增加_nghost-xxx属性,如果是ShadowDom模式下的被Shadow root 包裹的子元素则增加_ngcontent-xxx 属性。同时预处理css,更改对应的选择器。由于并没有创建Shadow Dom,所以全局样式生效。
背景点表示全局样式作用区域
Angular官网也明确指出:
在
@Component的元数据中指定的样式只会对该组件的模板生效。它们既不会被模板中嵌入的组件继承,也不会被通过内容投影(如 ng-content)嵌进来的组件继承。
app-list-item同时增加了属性_ngcontent-mgx-c17 和 _nghost-mgx-c16,表示宿主不是组件模版内容的一部分,而是父组件模板的一部分。 但是在宿主内部可以通过:host 伪类选择器来选择宿主元素。 如上图所示,如果想给在 app-list 的scss文件中想给 .my-app-list 类上写样式,应该这样写:
:host.my-app-list{
......
}
收获知识点
- Angular中为样式隔离提供了两种模式:ShadowDom 和 Emulated
- ShadowDom模式是使用浏览器原生的Shadow DOM实现,ShadowDom对外部的元素是隐藏的,因此,样式的表现为不进不出,全局样式进不来,组件样式出不去
- Emulated是Angular通过模拟Shadow DOM的行为实现的一种模式,并没有创建ShadowDom,只是给组件增加了额外的属性来标识 host 和 content 并预处理css选择器。组件样式表现为只进不出,全局样式能进来,组件样式出不去
- Emulated模式下宿主不是组件模版内容的一部分,而是父组件模板的一部分,但是在宿主内部可以通过
:host伪类选择器来选择宿主元素