65.9K
CodeProject 正在变化。 阅读更多。
Home

Angular 中的视图封装

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (4投票s)

2019 年 12 月 26 日

CPOL

6分钟阅读

viewsIcon

7305

downloadIcon

59

我们将了解 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 属性将 h1color 设置为 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 属性,而是使用 shadowrootinnerHtml 属性,这样,我们 h1 的样式就会作用于该 h1 元素,而不会泄露到该元素之外。这就是 shadowDOM

你可能会想,这与 Angular 有什么关系?答案是,正如我之前提到的,shadowDOM 目前并非所有浏览器都支持,因此为了实现这个功能,Angular 采用了自己的技巧,或者我们可以说 Angular 模拟了 shadowDOM

Angular 通过一个名为 ViewEncapsulation 的概念来实现 shadowDOM 的功能。这是一个 enum,有三个属性:

  • Emulated (模拟)
  • 原生

让我们通过代码看看这些值是如何工作的。

注意:在这里,我假设你们都知道什么是组件以及如何创建组件。我将使用 courses 组件来渲染课程列表。你可以使用自己的组件和逻辑,因为在演示此示例时,我们的主要关注点将是组件元数据,而不是组件内部的逻辑。

下面是我们简单的 courses 组件及其元数据。

为了实现 ViewEncapsulation,我们将使用 components 元数据的 encapsulation 属性,并为其分配 ViewEncapsulation enum 值,如下所示:

注意:要使用 metadataviewencapsulation 属性,我们必须从 @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 enumNative 属性是如何工作的。正如我们已经提到的,这并非所有浏览器都支持,但为了演示,我们将看看它在我的浏览器上是如何工作的。

在组件元数据中将 ViewEncapsulation enum 的值更改为 Native

现在,在浏览器中,点击检查按钮并打开浏览器控制台。同样,在 head 部分,我们有三个样式,但你可能会注意到没有第四个样式,而我们之前有后处理的 CSS 规则。

现在,如果你向下滚动,在 <app-courses> 元素下方,我们有 shadow-root(上面用黑色标记)。在 shadow-root 下方,我们有一个 style 元素,并且 span.active 类在这里应用。所以它没有额外的属性。所有样式都作用于此处。通过这种方式,Native 属性就能工作。

现在,ViewEncapsulation 的第三个属性是 None。如果我们在组件元数据中使用此属性,样式将会泄露到其他元素。

历史

  • 2019 年 12 月 26 日:初始版本
© . All rights reserved.