Angular 中的视图封装






4.91/5 (4投票s)
我们将了解 Angular 中如何实现视图封装。
引言
本文将详细介绍视图封装(作用域样式)是什么以及它在 Angular 中是如何实现的。
先决条件
我假设所有读者都对 Angular 2 及以上版本有所了解,知道什么是组件以及 CSS 如何应用于组件。
视图封装的理念
我们知道,在 Angular 中,应用于一个组件的 CSS 只作用于该组件,而不会泄露到外部模板。但这到底是如何实现的呢?
在我们深入了解之前,我们需要了解 Shadow DOM 的概念。ShadowDom
本质上是一种规范,它实现了 DOM 树和样式封装。简单来说,它允许我们为元素应用作用域样式,而不会影响到外部世界。这是浏览器的一项新特性,目前并非所有浏览器都支持。
我将向你展示 Shadow DOM 的实际应用。
var elm = document.querySelector('myfav');
elm.innerHtml = `<style>h1 {color : blue} </style>
<h1>Test</h1>`;
你们可能熟悉这段纯 JavaScript 代码。在这里,我们使用 querySelector
方法获取了 myfav
元素的引用(这里的 myfav
可以是任何 Angular 组件)。现在我们有了元素的引用,就可以通过 innerHtml
属性将 h1
的 color
设置为 blue
。
但这种方法存在一个问题。问题在于,这种样式会泄露到该元素之外。因此,如果我在其他地方还有另一个 h1
,这种样式也会应用到它上面。我们不希望这样。如果我们创建一个组件并为其设置一些样式,我们希望这些样式只作用于该组件。或者,在另一种情况下,假设我们想使用其他开发者开发的组件,他们可能在该组件中定义了一些样式,当我们将其引入我们的应用程序时,我们不希望这些样式覆盖我们应用程序中的样式,而这正是 Shadow DOM 发挥作用的地方。
我们可以修改上面的代码,只需添加一行代码即可使用 Shadow DOM,如下所示:
var elm = document.querySelector('myfav');
var root = elm.createShadowRoot();
root.innerHtml = `<style>h1 {color : blue} </style>
<h1>Test</h1>`;
所以在这里,在我们使用 elm
变量获取元素的引用后,我们只需调用 createShadowRoot()
方法,它将为该元素返回 shadowDOM
的根。然后,我们不使用元素引用的 innerHtml
属性,而是使用 shadowroot
的 innerHtml
属性,这样,我们 h1
的样式就会作用于该 h1
元素,而不会泄露到该元素之外。这就是 shadowDOM
。
你可能会想,这与 Angular 有什么关系?答案是,正如我之前提到的,shadowDOM
目前并非所有浏览器都支持,因此为了实现这个功能,Angular 采用了自己的技巧,或者我们可以说 Angular 模拟了 shadowDOM
。
Angular 通过一个名为 ViewEncapsulation
的概念来实现 shadowDOM
的功能。这是一个 enum
,有三个属性:
Emulated (模拟)
原生
无
让我们通过代码看看这些值是如何工作的。
注意:在这里,我假设你们都知道什么是组件以及如何创建组件。我将使用 courses 组件来渲染课程列表。你可以使用自己的组件和逻辑,因为在演示此示例时,我们的主要关注点将是组件元数据,而不是组件内部的逻辑。
下面是我们简单的 courses 组件及其元数据。
为了实现 ViewEncapsulation
,我们将使用 components
元数据的 encapsulation
属性,并为其分配 ViewEncapsulation enum
值,如下所示:
注意:要使用 metadata
的 viewencapsulation
属性,我们必须从 @angular/core
导入 ViewEncapsulation
。
当我们访问 localhost:4200
时,我们将看到下面的页面,其中列出了课程,并且默认情况下,第一个是选中的。
现在,我们将了解 Angular 如何实现作用域样式或 shadowDOM
功能。好的,在检查 Angular 应用程序的 HTML 时,你一定注意到所有 HTML 元素和自定义组件上都有 _ngcontent[id]
。这正是 Angular 实现作用域样式的关键,或者说起到了大部分作用。
再次打开浏览器控制台并检查渲染的 HTML。在 <head>
部分,将列出该 HTML 中使用的所有样式。在所有列出的样式中,展开我们在组件 CSS 文件中为组件使用的样式。
在这里,你可能会注意到 Angular 动态应用于 span.active
的 [_ngcontent-c1]
属性。你可能在猜测这个属性的作用。
为了回答这个问题,你可能在上面看到了我们的 <app-courses>
元素,这是我们的 courses 组件的主机元素。在 <app-courses>
元素内部,有一个 <div>
,在 <div>
内部,有一个 <span>
。你可以在这里看到 _ngcontent-c1
属性也在这里应用,这就是我们之前猜测的这个属性的作用。
因此,Angular 会向我们的元素附加一个属性,并使用该属性来后处理我们的 CSS,就像上面显示的示例一样,Angular 将 _ngcontent-c1
附加到 div
,并在我们的 CSS 中也使用它。这样,该 CSS 将只应用于同时具有 span.active
类和 _ngcontent-c1
属性的元素。如果你查看此文档中的任何地方,你都不会找到其他地方的 _ngcontent-c1
属性。此外,我们不必担心这个属性是如何生成的以及如何应用的。这完全是 Angular 的工作。
通过这种方式,Angular 试图在其框架中模拟 shadowDOM
的概念。此外,这是 Angular 的默认行为,意味着要实现模拟视图封装,我们不必在元数据中使用 encapsulation
属性。
现在,我们将了解 ViewEncapsulation enum
的 Native
属性是如何工作的。正如我们已经提到的,这并非所有浏览器都支持,但为了演示,我们将看看它在我的浏览器上是如何工作的。
在组件元数据中将 ViewEncapsulation enum
的值更改为 Native
。
现在,在浏览器中,点击检查按钮并打开浏览器控制台。同样,在 head
部分,我们有三个样式,但你可能会注意到没有第四个样式,而我们之前有后处理的 CSS 规则。
现在,如果你向下滚动,在 <app-courses>
元素下方,我们有 shadow-root
(上面用黑色标记)。在 shadow-root
下方,我们有一个 style
元素,并且 span.active
类在这里应用。所以它没有额外的属性。所有样式都作用于此处。通过这种方式,Native
属性就能工作。
现在,ViewEncapsulation
的第三个属性是 None
。如果我们在组件元数据中使用此属性,样式将会泄露到其他元素。
历史
- 2019 年 12 月 26 日:初始版本