组件样式

Angular 应用使用标准的 CSS 来设置样式。这意味着我们可以把关于 CSS 的那些知识和技能直接用于我们的 Angular 程序中,例如:样式表、选择器、规则以及媒体查询等。

另外,Angular 还能把组件样式捆绑在我们的组件上,以实现比标准样式表更加模块化的设计。

在本章中,我们将学到如何加载和使用这些组件样式

目录

你可以在Plunker上运行本章这些代码的在线例子 / 可下载的例子并下载这些代码。

使用组件样式

对于我们写的每个 Angular 组件来说,除了定义 HTML 模板之外,我们还要定义用于模板的 CSS 样式、 指定任意的选择器、规则和媒体查询。

实现方式之一,是在组件的元数据中设置styles属性。 styles属性可以接受一个包含 CSS 代码的字符串数组。 通常我们只给它一个字符串就行了,如同下例:

@Component({
  selector: 'hero-app',
  template: `
    <h1>Tour of Heroes</h1>
    <hero-app-main [hero]=hero></hero-app-main>`,
  styles: ['h1 { font-weight: normal; }']
})
export class HeroAppComponent {
/* . . . */
}

我们放在组件样式中的选择器,只会应用在组件自身的模板中。上面这个例子中的h1选择器只会对 HeroAppComponent模板中的<h1>标签生效,而对应用中其它地方的<h1>元素毫无影响。

这种模块化相对于 CSS 的传统工作方式是一个巨大的改进。

特殊的选择器

组件样式中有一些从影子(Shadow) DOM 样式范围领域(记录在W3CCSS Scoping Module Level 1中) 引入的特殊选择器

:host

使用:host伪类选择器,用来选择组件宿主元素中的元素(相对于组件模板内部的元素)。

:host {
  display: block;
  border: 1px solid black;
}

这是我们能以宿主元素为目标的唯一方式。除此之外,我们将没办法指定它, 因为宿主不是组件自身模板的一部分,而是父组件模板的一部分。

要把宿主样式作为条件,就要像函数一样把其它选择器放在:host后面的括号中。

在下一个例子中,我们又一次把宿主元素作为目标,但是只有当它同时带有active CSS 类的时候才会生效。

:host(.active) {
  border-width: 3px;
}

:host-context

有时候,基于某些来自组件视图外部的条件应用样式是很有用的。 例如,在文档的<body>元素上可能有一个用于表示样式主题 (theme) 的 CSS 类,而我们应当基于它来决定组件的样式。

这时可以使用:host-context()伪类选择器。它也以类似:host()形式使用。它在当前组件宿主元素的祖先节点中查找 CSS 类, 直到文档的根节点为止。在与其它选择器组合使用时,它非常有用。

在下面的例子中,只有当某个祖先元素有 CSS 类theme-light时,我们才会把background-color样式应用到组件内部的所有<h2>元素中。

:host-context(.theme-light) h2 {
  background-color: #eef;
}

/deep/

组件样式通常只会作用于组件自身的 HTML 上。

我们可以使用/deep/选择器,来强制一个样式对各级子组件的视图也生效,它不但作用于组件的子视图,也会作用于组件的内容

在这个例子中,我们以所有的<h3>元素为目标,从宿主元素到当前元素再到 DOM 中的所有子元素:

:host /deep/ h3 {
  font-style: italic;
}

/deep/选择器还有一个别名>>>。我们可以任意交替使用它们。

/deep/>>>选择器只能被用在仿真 (emulated) 模式下。 这种方式是默认值,也是用得最多的方式。 更多信息,见控制视图封装模式一节。

把样式加载进组件中

有几种方式把样式加入组件:

上述作用域规则对所有这些加载模式都适用。

元数据中的样式

我们可以给@Component装饰器添加一个styles数组型属性。 这个数组中的每一个字符串(通常也只有一个)定义一份 CSS。

  1. @Component({
  2. selector: 'hero-app',
  3. template: `
  4. <h1>Tour of Heroes</h1>
  5. <hero-app-main [hero]=hero></hero-app-main>`,
  6. styles: ['h1 { font-weight: normal; }']
  7. })
  8. export class HeroAppComponent {
  9. /* . . . */
  10. }

元数据中指定样式表的URL

通过在组件的@Component装饰器中添加styleUrls属性,我们可以从外部CSS文件中加载样式:

  1. @Component({
  2. selector: 'hero-details',
  3. template: `
  4. <h2>{{hero.name}}</h2>
  5. <hero-team [hero]=hero></hero-team>
  6. <ng-content></ng-content>
  7. `,
  8. styleUrls: ['app/hero-details.component.css']
  9. })
  10. export class HeroDetailsComponent {
  11. /* . . . */
  12. }

URL是相对于应用程序根目录的,它通常是应用的宿主页面index.html所在的地方。 这个样式文件的 URL 不是相对于组件文件的。这就是为什么范例中的 URL 用src/app/开头。 参见附录 2 来了解如何指定相对于组件文件的 URL。

像 Webpack 这类模块打包器的用户可能会使用styles属性来在构建时从外部文件中加载样式。它们可能这样写:

styles: [require('my.component.css')]

注意,这时候我们是在设置styles属性,而不是styleUrls属性! 是模块打包器在加载 CSS 字符串,而不是 Angular。 Angular 看到的只是打包器加载它们之后的 CSS 字符串。 对 Angular 来说,这跟我们手写了styles数组没有任何区别。 要了解这种 CSS 加载方式的更多信息,请参阅相应模块打包器的文档。

模板内联样式

我们也可以在组件的 HTML 模板中嵌入<style>标签。

  1. @Component({
  2. selector: 'hero-controls',
  3. template: `
  4. <style>
  5. button {
  6. background-color: white;
  7. border: 1px solid #777;
  8. }
  9. </style>
  10. <h3>Controls</h3>
  11. <button (click)="activate()">Activate</button>
  12. `
  13. })

我们也可以在组件的 HTML 模板中嵌入<link>标签。

styleUrls标签一样,这个 link 标签的href指向的 URL 也是相对于应用的根目录的,而不是组件文件。

  1. @Component({
  2. selector: 'hero-team',
  3. template: `
  4. <link rel="stylesheet" href="app/hero-team.component.css">
  5. <h3>Team</h3>
  6. <ul>
  7. <li *ngFor="let member of hero.team">
  8. {{member}}
  9. </li>
  10. </ul>`
  11. })

CSS @imports

我们还可以利用标准的 CSS @import规则来把其它 CSS 文件导入到我们的 CSS 文件中。

这种情况下,URL 是相对于我们执行导入操作的 CSS 文件的。

src/app/hero-details.component.css (excerpt)

@import 'hero-details-box.css';

控制视图的封装模式:原生 (Native)、仿真 (Emulated) 和无 (None)

像上面讨论过的一样,组件的 CSS 样式被封装进了自己的视图中,而不会影响到应用程序的其它部分。

通过在组件的元数据上设置视图封装模式,我们可以分别控制每个组件的封装模式。 可选的封装模式一共有如下几种:

通过组件元数据中的encapsulation属性来设置组件封装模式:

// warning: few browsers support shadow DOM encapsulation at this time
encapsulation: ViewEncapsulation.Native

原生(Native)模式只适用于有原生 Shadow DOM 支持的浏览器。 因此仍然受到很多限制,这就是为什么我们会把仿真 (Emulated) 模式作为默认选项,并建议将其用于大多数情况。

附录 1:查看仿真 (Emulated) 模式下生成的 CSS

当使用默认的仿真模式时,Angular 会对组件的所有样式进行预处理,让它们模仿出标准的 Shadow CSS 作用域规则。

当我们查看启用了仿真模式的 Angular 应用时,我们看到每个 DOM 元素都被加上了一些额外的属性。

<hero-details _nghost-pmm-5>
  <h2 _ngcontent-pmm-5>Mister Fantastic</h2>
  <hero-team _ngcontent-pmm-5 _nghost-pmm-6>
    <h3 _ngcontent-pmm-6>Team</h3>
  </hero-team>
</hero-detail>

我们看到了两种被生成的属性:

这些属性的具体值并不重要。它们是自动生成的,并且我们永远不会在程序代码中直接引用到它们。 但它们会作为生成的组件样式的目标,就像我们在 DOM 的<head>区所看到的:

[_nghost-pmm-5] {
  display: block;
  border: 1px solid black;
}

h3[_ngcontent-pmm-6] {
  background-color: white;
  border: 1px solid #777;
}

这些就是我们写的那些样式被处理后的结果,于是每个选择器都被增加了_nghost_ngcontent属性选择器。 在这些附加选择器的帮助下,我们实现了本指南中所描述的这些作用域规则。

附录 2:使用相对 URL 加载样式

把组件的代码 (ts/js)、HTML 和 CSS 分别放到同一个目录下的三个不同文件,是一个常用的实践:

quest-summary.component.ts
quest-summary.component.html
quest-summary.component.css

我们会通过设置元数据的templateUrlstyleUrls属性把模板和 CSS 文件包含进来。 既然这些文件都与组件(代码)文件放在一起,那么通过名字,而不是到应用程序根目录的全路径来指定它,就会是一个漂亮的方式。

我们也可以通过为文件名加上./前缀来使用相对URL:

src/app/quest-summary.component.ts

  1. @Component({
  2. selector: 'quest-summary',
  3. templateUrl: './quest-summary.component.html',
  4. styleUrls: ['./quest-summary.component.css']
  5. })
  6. export class QuestSummaryComponent { }