前言

要掌握 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)的更多相关文章

  1. Angular CLI 使用教程指南参考

    Angular CLI 使用教程指南参考 Angular CLI 现在虽然可以正常使用但仍然处于测试阶段. Angular CLI 依赖 Node 4 和 NPM 3 或更高版本. 安装 要安装Ang ...

  2. Angular入门到精通系列教程(7)- 组件(@Component)基本知识

    1. 概述 2. 创建Component 组件模板 视图封装模式 特殊的选择器 :host inline-styles 3. 总结 环境: Angular CLI: 11.0.6 Angular: 1 ...

  3. Vue教程:组件Component详解(六)

    一.什么是组件? 组件 (Component) 是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功 ...

  4. MVC+EFCore 完整教程18 -- 升级分布视图至 View Component

    之前我们详细介绍过分布视图(partial view),在有一些更加复杂的场景下,.net core为我们提供了更加强大的组件 view  component, 可以认为view component是 ...

  5. angular2 学习笔记 ( Component 组件)

    refer : https://angular.cn/docs/ts/latest/guide/template-syntax.html https://angular.cn/docs/ts/late ...

  6. 一篇文章看懂angularjs component组件

     壹 ❀ 引 我在 angularjs 一篇文章看懂自定义指令directive 一文中详细介绍了directive基本用法与完整属性介绍.directive是个很神奇的存在,你可以不设置templa ...

  7. Angular 英雄示例教程

    英雄指南教程(Tour of Heroes)涵盖了 Angular 的基本知识. 在本教程中,你将构建一个应用,来帮助人事代理机构来管理一群英雄. 这个入门级 app 包含很多数据驱动的应用所需的特性 ...

  8. [从 0 开始的 Angular 生活]No.38 实现一个 Angular Router 切换组件页面(一)

    前言 今天是进入公司的第三天,为了能尽快投入项目与成为团队可用的战力,我正在努力啃官方文档学习 Angular 的知识,所以这一篇文章主要是记录我如何阅读官方文档后,实现这个非常基本的.带导航的网页应 ...

  9. NgRx/Store 4 + Angular 5使用教程

    这篇文章将会示范如何使用NgRx/Store 4和Angular5.@ngrx/store是基于RxJS的状态管理库,其灵感来源于Redux.在NgRx中,状态是由一个包含action和reducer ...

  10. Component(组件)

    1.Component是一个模板的控制类用于处理应用和逻辑页面的视图部分. 2.Component时Angular2应用最基础的建筑砖块. 3.任何一个Component都是NgModule的一部分, ...

随机推荐

  1. Solo 开发者周刊 (第3期):如何打造令人惊艳的AI体验

    这里会整合 Solo 社区每周推广内容.产品模块或活动投稿,每周五发布.在这期周刊中,我们将深入探讨开源软件产品的开发旅程,分享来自一线独立开发者的经验和见解.本杂志开源,欢迎投稿. 好文推荐 Plu ...

  2. 【实操记录】MySQL二进制安装包部署

    截至2023年11月2日,MySQL社区版最新版本是8.0.35,本文详细描述了采用二进制安装的各个步骤,具有较强的参考意义,基本可作为标准步骤实施. ■ 下载数据库介质 社区版的下载地址为oracl ...

  3. 数据仓库建模工具之一——Hive学习第二天

    Hive的概述 1.Hive基本概念 1.1 Hive简介 Hive本质是将SQL转换为MapReduce的任务进行运算,底层由HDFS来提供数据存储,说白了hive可以理解为一个将SQL转换为Map ...

  4. 为什么学编程都从helloworld开始?

    你好世界 回忆上次内容 上次 了解了 游乐场规则 REPL       添加图片注释,不超过 140 字(可选)   print函数 可以输出 字符串"h"     添加图片注释, ...

  5. 如何理解计算机类论文、机器学习论文、人工智能AI论文中的“soft”和“hard”呢?

    如何理解计算机类论文.机器学习论文.人工智能AI论文中的"soft"和"hard"呢? 最近在看论文中总看到带有"soft"和"h ...

  6. Apache DolphinScheduler用户线上Meetup火热来袭!

    Apache DolphinScheduler 社区 8 月用户交流会精彩继续!本次活动邀请到老牌农牧产品实业集团铁骑力士架构工程师,来分享Apache DolphinScheduler在现代农牧食品 ...

  7. 2024-08-24:用go语言,给定一个下标从1开始,包含不同整数的数组 nums,数组长度为 n。 你需要按照以下规则进行 n 次操作,将数组 nums 中的所有元素分配到两个新数组 arr1 和

    2024-08-24:用go语言,给定一个下标从1开始,包含不同整数的数组 nums,数组长度为 n. 你需要按照以下规则进行 n 次操作,将数组 nums 中的所有元素分配到两个新数组 arr1 和 ...

  8. 树上倍增求 LCA 模板

    void dfs(int x,int fa,int d){ deep[x]=d;dp[x][0]=fa; for(int i=1;i<=lg2[deep[x]];++i){ dp[x][i]=d ...

  9. [kernel] 带着问题看源码 —— 脚本是如何被 execve 调用的

    前言 在<[apue] 进程控制那些事儿>一文的"进程创建-> exec -> 解释器文件"一节中,曾提到脚本文件的识别是由内核作为 exec 系统调用处理 ...

  10. k8s网络原理之Calico

    什么是Calico: Calico是一个基于BGP的纯三层网络方案,其会为每个容器(pod)分配一个可路由的IP,在通信时不需要解包和拆包,因此网络性能损耗小,易于排查和水平扩展.Calico网络功能 ...