Angular 18+ 高级教程 – Component 组件 の Angular Component vs Shadow DOM (CSS Isolation & slot)
前言
要掌握 Angular,最好先掌握原生。
全局 CSS 的问题,还有如何用原生 CSS 来管理全局 CSS,看这篇。
利用 Shadow Dom 来隔离 CSS 看这篇。
CSS Global Effect
CSS style 是全局影响的。
假设我们有 2 个组件,AppComponent 和 TestComponent。
app html
<div class="container">
<h1>Outside Hello World</h1>
<app-test></app-test>
</div>
app css
h1 {
background-color: pink;
}
test html
<h1>Inside Hello World</h1>
test css
h1 {
font-size: 48px;
color: red;
}
两个组件 html 都有 h1 element,同时 2 个组件的 css 都给予 h1 不同的 style(1 个给 background-color, 1 个给 color)
最终效果
两个 h1 element 都渲染了 2 个 styles(background-color & color)这就是 CSS 默认的行为。
但总所周知,全局是魔鬼。所以 CSSer 有许许多多方法来解决这个问题,比如 BEM。 而 Angular 也用了其中的二种方案。
Angular CSS Isolation
如果你按照我上面的例子写,你会发现出来的效果 style 并没有互相渲染。这是因为 Angular 默认就开启了 CSS 隔离。(我是为了演示特意关闭的)
Angular 有 2 种隔离方案,它们都可以达到隔离效果,但有一点点微小的区别。
Shadow DOM 隔离方案
第一种方案是 Shadow DOM,Shadow DOM 的主要功能本来就是隔离。所以 Angular 会选择这种方案是显而易见的。
我们在组件的 metadata 里声明
encapsulation: ViewEncapsulation.ShadowDom
这样就开启了 Shadow DOM 隔离
和
效果
element 结构
Shadow DOM 隔离方案的概念是 “不能进,不能出",外面 (其它组件或者全局) CSS 不能进入到组件里,同时组件定义的 CSS 也不会跑出去影响其组件。
Emulated Shadow DOM 隔离方案
encapsulation: ViewEncapsulation.Emulated
有 Shadow DOM 方案不就够了吗?为什么还要搞多一个 Emulated (模拟) 方案呢?而且这个模拟方案竟然还是 default(首选)方案!?
Shadow DOM 会隔离 "所有" 外部的 style。这也意味着我们不可以写全局的 reset.css、base.css 等等。
虽然我们想隔离,但是我们想隔离的是各个组件之间的 style,而不是像 reset.css 这种通用的全局 style。
为此 Angular 就搞了一个 Emulated Shadow DOM,它的概念是 "能进,不能出",和 Shadow DOM 一样不允许组件的 style 跑出去,但它允许外面的 style 跑进来。
Angular 项目中全部都是组件,所有的 style 都跑不出去,唯一能跑进来的就只剩下全局 style 了。
这样各个组件既不会互相影响,同时有能拿到全局 style,完美
这个 styles.scss 就可以让我们写全局 reset 和 base style。
Emulated 的具体实现手法类似 BEM,通过给 class selector 长命名来起到相互不被影响,它整个过程是在 compile 阶段完成的,所以我们完全感受不到。
下面是最终生成出来的 HTML 和 style:
element 和 CSS selector 都多了许多 attribute, _ngcontent-ng-c123456789
这样就实现了隔离效果。
关闭所有隔离方案
上面有提到,Angular 默认就替组件开启了 Emulated 隔离方案,所以哪怕我们什么也没声明,它就已经是隔离的了。
但如果我们想关关闭隔离方案也是可以的,只要声明
encapsulation: ViewEncapsulation.None
这样就关闭了。
它的概念是 "能进,能出",全局 style 可以进入到此组件,同时此组件定义的 CSS 也会跑出去 (相等于定义了全局 style) 影响其它组件。
Shadow DOM Slot vs Angular Content Projection (a.k.a slot / transclude / ng-content)
Shadow DOM 可以通过 slot 从外部 transclude element 到 Shadow DOM 里面。
Angular 也有这个能力,但 Angular 并不是用原生 slot 来实现的。不管是 Emulated mode 还是 ShadowDom mode,Angular 都不是用原生 slot。
ng-content
app.component.html
<app-test>
<h1>Hello World</h1>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit.</p>
</app-test>
有一个 test component,我们要从外部 translude h1 和 p 进去。
test.component.html
<div class="container">
<h1>This is inside content</h1>
<h1>Below is outside content:</h1>
<div class="outside-content-area">
<ng-content></ng-content>
</div>
</div>
如果是原生 Shadow DOM,那么里面应该用 <slot> element。
而 Angular 则使用 <ng-content> element。
功能都是一样的 transclude outside element to inside。
效果
注:关于 transclude element 的 style,Angular 和 Shadow DOM 是一样,style 由外部设置。
你可以这样去理解. transclude element 在外部渲染好了以后,被 cut and paste 到内部。
default ng-content (a.k.a fallback content)
Angular 直到 v18.0 才支持 default ng-content 功能...
这个功能的 Github feature request:ng-content default content 是在 2016-10-26 提的...一个功能需要 8 年时间做...
用法很简单
<ng-content>
<h1>default content</h1>
</ng-content>
在 ng-content 内放入 element 就可以了,如果外部没有传 element 进来,那就会用 default 的 element。
Check if empty ng-content?
default ng-content 只能让你在 empty content 的时候放入一个 default content,但它无法让你判断是否是 empty 做任意的事情。
比如:hide parent when empty content
<span class="left-part">
<span class="icon-wrapper"><ng-content select="mat-icon" /></span>
<span class="text">
<span class="line1"><ng-content /></span>
<span class="line2">{{ textLine2() }}</span>
</span>
</span>
假如没有传入 mat-icon,我希望把 icon-wrapper 给 display none。
参考:Stack Overflow – How to check whether <ng-content> is empty? (in Angular 2+ till now)
使用 CSS seelctor 是一个不错的办法,但这招也只能针对这个需求,其它需求还得想其它办法。
总之 Angular 没有提供一个简单通用的方案就是了
multiple ng-content
Shadow DOM 的 slot 支持 multiple,Angular 的 ng-content 也支持。
我们把 translude element 分成 2 个部分。first and second
<app-test>
<h1 slot="first">Hello World</h1>
<p slot="second">Lorem, ipsum dolor sit amet consectetur adipisicing elit.</p>
</app-test>
然后 component 里面也分成 2 个 display areas
<div class="container">
<h1 part="inside-h1">This is inside content</h1>
<h1>Below is outside content:</h1> <div class="outside-content-area">
<ng-content select="[slot='first']"></ng-content>
</div> <div class="outside-content-area">
<ng-content select="[slot='second']"></ng-content>
</div>
</div>
对比原生 slot 和 Angular ng-content 的语法
outside component:
语法一致.
inside component:
原生 slot 是通过 name attribute = "outside slot name"。
Angular 则是通过 select attribute = "any css selector"。
所以,对比这个环节。Angular 其实是更加方面的,因为它支持任何 selector 方式。我们不一定要用 slot="first" 也可以用 class 或 element tag 去 select。比如:
这样也是 ok 的.
Only first layer can be select
App Template
<app-test>
<h1>Hello World</h1>
</app-test>
Test Template
<ng-content select="h1"></ng-content>
上面这样 ok。
但如果我们把 h1 wrap 起来就不行了
<app-test>
<div>
<h1>Hello World</h1>
</div>
</app-test>
总之,只有 first layer child 才可以被 select,这是 Angular 的 limitation。
ngProjectAs
有一个 HelloWorld 组件,里面 select h1 作为 ng-content。
可是呢,外部因为某种说不出的原因,只能使用 h2
<app-hello-world>
<h2>content 1</h2>
</app-hello-world>
这样就 match 不到了丫,怎么办呢?
这时,我们可以使用 ngProjectAs="what-ever-selector"
<h2 ngProjectAs="h1">content 1</h2>
我们可以把 h2 变成任何 selector,这样 HelloWorld 组件内部就可以 select 到了。
<ng-container> wrapper
假设内部 select by class "content-1" 和 "content-2"
<ng-content select=".content-1"></ng-content>
<ng-content select=".content-2"></ng-content>
外部长这样
<app-hello-world>
<h1 class="content-1">content 1</h1>
<h2 class="content-2">Content 2</h2>
<h3 class="content-2">Content 3</h3>
<h4 class="content-2">Content 4</h4>
</app-hello-world>
h2, h3, h4 都是 content-2,每一个都需要写上 class="content-2"。
那有没有一种方式可以不必重复写 class 呢?
有,那就是用 <ng-container> wrap 它
<ng-container class="content-2">
<h2>Content 2</h2>
<h3>Content 3</h3>
<h4>Content 4</h4>
</ng-container>
<ng-container> 的用途很广,以后会详细讲解,这里我们只要知道它是 Angular 的一个特别的 syntax,
它不是组件,也不是 Element,它被用于 compile 阶段,compile 结束后它会变成一个 Comment 节点 (<!--ng-container-->)。
上面例子中,<ng-container> 起到了一个 wrapper 作用,Angular 在 compile 阶段可以知道 h2, h3, h4 是一组的,它们都属于 content-2。
而在 compile 后,它会变成这样
可以看到 ng-container 并没有再 wrap 着 h2, h3, h4,只是在结尾多了一个 Comment 节点。
它这样就做到了在不严重破坏 DOM 结构的前提下,提供 wrapper 概念给 compiler。
在 <ng-container> 上写 DOM 属性 (比如 class="content2") 是不顺风水的,毕竟人家不是真的 Element。更好的做法是配上 ngProjectAs。
<ng-container ngProjectAs=".content-2">
<h2>Content 2</h2>
<h3>Content 3</h3>
<h4>Content 4</h4>
</ng-container>
提醒:wrap 了 <ng-container> 一定要给 class 或者 ngProjectAs 哦,否则 <ng-content> 会 select 不到。
原因上面我们说过了 -- only first layer child can be select。一旦 wrap 了 <ng-container> h2, h3, h4 就不算是 first layer child 了 (即使在 compile render 后 <ng-container> 会变成 comment 并且不再 wrap 着 h2, h3, h4)
:host, :host-context
Angular 支持 :host,也支持 :host-context()。它和 Native Shadow DOM 用法、效果都是一致的。
而且,Angular 还解决了 :host-context 在 Firefox 和 Safari 下不支持的问题哦。
提醒::host-context 不只是看 host element 有没有 matched CSS selector,它也会看所有 ancestor (祖先) elements,只要其中一个 match 就算有。
比如说 :host-context(.my-class),<body class="my-class"> 这样也算 matched。
::slotted()、::part()、::ng-deep
Angular 完全不支持 ::slotted(),因为 Angular 没有 slot element 所以 select 不到。
Angular 在 Emulated mode 下,不支持 ::part(),但是在 ShadowDom mode 下支持。
在 Angular 圈,没有人愿意用那么乱的东西,一些支持,一些又不支持的。
大部分 Angular 人会统一使用 Emulated mode + ::ng-deep 来替代 ::slotted() 和 ::part()。
注意:
::ng-deep 一定要搭配 Emulated mode 才有效哦。另外,它是一个废弃了很多年的语法,但时至今日它依然可以用。这是因为 Angular 一直没有方法可以实现 ::slotted() 和 ::part(),所以只能暂时让 ::ng-deep 负责。
::ng-deep 的功能是 by pass 所有 CSS 隔离,selector 可以渗透到 component 内(不管多少层 component 都渗透哦)
::ng-deep 替代 ::part()
app-test::part(inside-h1) {
background-color: green;
} app-test ::ng-deep [part="inside-h1"] {
background-color: gray;
}
::ng-deep 可以配任何 selector, 不一定要搭配 attribute part 来用.
::ng-deep 替代 ::slotted()
::slotted(h1) {
background-color: green;
} ::ng-deep h1 {
background-color: green;
}
目录
上一篇 Angular 18+ 高级教程 – Component 组件 の Angular Component vs Custom Elements
下一篇 Angular 18+ 高级教程 – Component 组件 の Template Binding Syntax
想查看目录,请移步 Angular 18+ 高级教程 – 目录
喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding
Angular 18+ 高级教程 – Component 组件 の Angular Component vs Shadow DOM (CSS Isolation & slot)的更多相关文章
- Angular CLI 使用教程指南参考
Angular CLI 使用教程指南参考 Angular CLI 现在虽然可以正常使用但仍然处于测试阶段. Angular CLI 依赖 Node 4 和 NPM 3 或更高版本. 安装 要安装Ang ...
- Angular入门到精通系列教程(7)- 组件(@Component)基本知识
1. 概述 2. 创建Component 组件模板 视图封装模式 特殊的选择器 :host inline-styles 3. 总结 环境: Angular CLI: 11.0.6 Angular: 1 ...
- Vue教程:组件Component详解(六)
一.什么是组件? 组件 (Component) 是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功 ...
- MVC+EFCore 完整教程18 -- 升级分布视图至 View Component
之前我们详细介绍过分布视图(partial view),在有一些更加复杂的场景下,.net core为我们提供了更加强大的组件 view component, 可以认为view component是 ...
- angular2 学习笔记 ( Component 组件)
refer : https://angular.cn/docs/ts/latest/guide/template-syntax.html https://angular.cn/docs/ts/late ...
- 一篇文章看懂angularjs component组件
壹 ❀ 引 我在 angularjs 一篇文章看懂自定义指令directive 一文中详细介绍了directive基本用法与完整属性介绍.directive是个很神奇的存在,你可以不设置templa ...
- Angular 英雄示例教程
英雄指南教程(Tour of Heroes)涵盖了 Angular 的基本知识. 在本教程中,你将构建一个应用,来帮助人事代理机构来管理一群英雄. 这个入门级 app 包含很多数据驱动的应用所需的特性 ...
- [从 0 开始的 Angular 生活]No.38 实现一个 Angular Router 切换组件页面(一)
前言 今天是进入公司的第三天,为了能尽快投入项目与成为团队可用的战力,我正在努力啃官方文档学习 Angular 的知识,所以这一篇文章主要是记录我如何阅读官方文档后,实现这个非常基本的.带导航的网页应 ...
- NgRx/Store 4 + Angular 5使用教程
这篇文章将会示范如何使用NgRx/Store 4和Angular5.@ngrx/store是基于RxJS的状态管理库,其灵感来源于Redux.在NgRx中,状态是由一个包含action和reducer ...
- Component(组件)
1.Component是一个模板的控制类用于处理应用和逻辑页面的视图部分. 2.Component时Angular2应用最基础的建筑砖块. 3.任何一个Component都是NgModule的一部分, ...
随机推荐
- 全网最适合入门的面向对象编程教程:12 类和对象的Python实现-Python使用logging模块输出程序运行日志
全网最适合入门的面向对象编程教程:12 类和对象的 Python 实现-Python 使用 logging 模块输出程序运行日志 摘要: 本文主要介绍了日志的定义和作用,以及 Python 内置日志处 ...
- 在windows双系统中,nginx配置虚拟域名
比如在ubuntu系统中,nginx配置了域名www.abc.com, 那么需要在终端 sudo vim /etc/hosts文件中配置域名,如下: 127.0.0.1 www.abc.com 即可访 ...
- vue 理解yarn start 和yarn dev的区别
yarn dev,当文件变动后,会自动重启. yanr start不会自动重启 nodemon会监听文件变动,跟yarn dev和yarn start无关.
- <script> 和 <script setup> 的一些主要差别
<script setup> 是 Vue 3 中的新特性,它是一种简化和更具声明性的语法,用于编写组件的逻辑部分.相比之下,<script> 是 Vue 2 中常用的编写组件逻 ...
- [oeasy]python0123_中文字符_文字编码_gb2312_激光照排技术_王选
中文编码GB2312 回忆上次内容 上次回顾了 日韩各有 编码格式 日本 有假名 五十音 一字节 可以勉强放下 有日本汉字 字符数量超过20000+ 韩国 有谚文 数量超过500 一个字节 ...
- odoo 开发入门教程系列-一个新应用
一个新应用 房地产广告模块 假设需要开发一个房地产模块,该模块覆盖未包含在标准模块集中特定业务领域. 以下为包含一些广告的主列表视图 form视图顶层区域概括了房产的重要信息,比如name,Prope ...
- Activity活动生命相关
启动与结束 页面跳转: startActivity(new Intent(this,xxxx.class)); 关闭当前界面返回上一界面 finish(); //这里我在使用finish遇到一个问题, ...
- 【Java】Jsoup 解析HTML报告
一.需求背景 有好几种报告文件,目前是人肉找报告信息填到Excel上生成统计信息 跟用户交流了下需求和提供的几个文件,发现都是html文件 其实所谓的报告的文件,就是一些本地可打开的静态资源,里面也有 ...
- 【Mybatis】07 万用Map & 模糊查询细节
这应该算一个补充点 在尚硅谷的Java笔记资料有专门的一起万用Map讲解 参考自:https://www.bilibili.com/video/BV1NE411Q7Nx?p=5 万用Map 我们可以使 ...
- MindSpore框架 加载文本数据集 示例
代码原地址: https://www.mindspore.cn/tutorial/training/zh-CN/r1.2/use/load_dataset_text.html ============ ...