组件样式

Component Styles

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

Angular applications are styled with standard CSS. That means you can apply everything you know about CSS stylesheets, selectors, rules, and media queries directly to Angular applications.

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

Additionally, Angular can bundle component styles with components, enabling a more modular design than regular stylesheets.

本章将会讲解如何加载和使用这些组件样式

This page describes how to load and apply these component styles.

你可以运行在线例子 / 下载范例,在 Stackblitz 中试用并下载本页的代码。

You can run the在线例子 / 下载范例in Stackblitz and download the code from there.

使用组件样式

Using component styles

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

For every Angular component you write, you may define not only an HTML template, but also the CSS styles that go with that template, specifying any selectors, rules, and media queries that you need.

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

One way to do this is to set the styles property in the component metadata. The styles property takes an array of strings that contain CSS code. Usually you give it one string, as in the following example:

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

范围化的样式

Style scope

@Component 的元数据中指定的样式只会对该组件的模板生效。

The styles specified in @Component metadata apply only within the template of that component.

它们既不会被模板中嵌入的组件继承,也不会被通过内容投影(如 ng-content)嵌进来的组件继承。

They are not inherited by any components nested within the template nor by any content projected into the component.

在这个例子中,h1 的样式只对 HeroAppComponent 生效,既不会作用于内嵌的 HeroMainComponent ,也不会作用于应用中其它任何地方的 <h1> 标签。

In this example, the h1 style applies only to the HeroAppComponent, not to the nested HeroMainComponent nor to <h1> tags anywhere else in the application.

这种范围限制就是所谓的样式模块化特性

This scoping restriction is a styling modularity feature.

  • 可以使用对每个组件最有意义的 CSS 类名和选择器。

    You can use the CSS class names and selectors that make the most sense in the context of each component.

  • 类名和选择器是局限于该组件的,它不会和应用中其它地方的类名和选择器冲突。

    Class names and selectors are local to the component and don't collide with classes and selectors used elsewhere in the application.

  • 组件的样式不会因为别的地方修改了样式而被意外改变。

    Changes to styles elsewhere in the application don't affect the component's styles.

  • 你可以让每个组件的 CSS 代码和它的 TypeScript、HTML 代码放在一起,这将促成清爽整洁的项目结构。

    You can co-locate the CSS code of each component with the TypeScript and HTML code of the component, which leads to a neat and tidy project structure.

  • 将来你可以修改或移除组件的 CSS 代码,而不用遍历整个应用来看它有没有在别处用到。

    You can change or remove component CSS code without searching through the whole application to find where else the code is used.

特殊的选择器

Special selectors

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

Component styles have a few special selectors from the world of shadow DOM style scoping (described in the CSS Scoping Module Level 1 page on the W3C site). The following sections describe these selectors.

:host

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

Use the :host pseudo-class selector to target styles in the element that hosts the component (as opposed to targeting elements inside the component's template).

:host { display: block; border: 1px solid black; }
src/app/hero-details.component.css
      
      :host {
  display: block;
  border: 1px solid black;
}
    

:host 选择是是把宿主元素作为目标的唯一方式。除此之外,你将没办法指定它, 因为宿主不是组件自身模板的一部分,而是父组件模板的一部分。

The :host selector is the only way to target the host element. You can't reach the host element from inside the component with other selectors because it's not part of the component's own template. The host element is in a parent component's template.

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

Use the function form to apply host styles conditionally by including another selector inside parentheses after :host.

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

The next example targets the host element again, but only when it also has the active CSS class.

:host(.active) { border-width: 3px; }
src/app/hero-details.component.css
      
      :host(.active) {
  border-width: 3px;
}
    

:host-context

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

Sometimes it's useful to apply styles based on some condition outside of a component's view. For example, a CSS theme class could be applied to the document <body> element, and you want to change how your component looks based on that.

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

Use the :host-context() pseudo-class selector, which works just like the function form of :host(). The :host-context() selector looks for a CSS class in any ancestor of the component host element, up to the document root. The :host-context() selector is useful when combined with another selector.

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

The following example applies a background-color style to all <h2> elements inside the component, only if some ancestor element has the CSS class theme-light.

:host-context(.theme-light) h2 { background-color: #eef; }
src/app/hero-details.component.css
      
      :host-context(.theme-light) h2 {
  background-color: #eef;
}
    

已废弃 /deep/>>>::ng-deep

(deprecated) /deep/, >>>, and ::ng-deep

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

Component styles normally apply only to the HTML in the component's own template.

可以使用 /deep/ 选择器来强制一个样式对各级子组件的视图也生效,它不但会作用于组件的子视图,也会作用于投影进来的内容(ng-content)。

Use the /deep/ shadow-piercing descendant combinator to force a style down through the child component tree into all the child component views. The /deep/ combinator works to any depth of nested components, and it applies to both the view children and content children of the component.

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

The following example targets all <h3> elements, from the host element down through this component to all of its child elements in the DOM.

:host /deep/ h3 { font-style: italic; }
src/app/hero-details.component.css
      
      :host /deep/ h3 {
  font-style: italic;
}
    

/deep/ 组合器还有两个别名:>>>::ng-deep

The /deep/ combinator also has the aliases >>>, and ::ng-deep.

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

Use /deep/, >>> and ::ng-deep only with emulated view encapsulation. Emulated is the default and most commonly used view encapsulation. For more information, see the Controlling view encapsulation section.

CSS 标准中用于 "刺穿 Shadow DOM" 的组合器已经被废弃,并将这个特性从主流浏览器和工具中移除。 因此,我们也将在 Angular 中移除对它们的支持(包括 /deep/>>>::ng-deep)。 目前,建议先统一使用 ::ng-deep,以便兼容将来的工具。

The shadow-piercing descendant combinator is deprecated and support is being removed from major browsers and tools. As such we plan to drop support in Angular (for all 3 of /deep/, >>> and ::ng-deep). Until then ::ng-deep should be preferred for a broader compatibility with the tools.

把样式加载进组件中

Loading component styles

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

There are several ways to add styles to a component:

  • 设置 stylesstyleUrls 元数据

    By setting styles or styleUrls metadata.

  • 内联在模板的 HTML 中

    Inline in the template HTML.

  • 通过 CSS 文件导入

    With CSS imports.

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

The scoping rules outlined earlier apply to each of these loading patterns.

元数据中的样式

Styles in component metadata

你可以给 @Component 装饰器添加一个 styles 数组型属性。

You can add a styles array property to the @Component decorator.

这个数组中的每一个字符串(通常也只有一个)定义一份 CSS。

Each string in the array defines some CSS for this component.

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

注意:这些样式只对当前组件生效。 它们既不会作用于模板中嵌入的任何组件,也不会作用于投影进来的组件(如 ng-content )。

Reminder: these styles apply only to this component. They are not inherited by any components nested within the template nor by any content projected into the component.

当使用 --inline-styles 标识创建组件时,Angular CLI 的 ng generate component命令就会定义一个空的 styles 数组

The Angular CLI command ng generate componentdefines an empty styles array when you create the component with the --inline-style flag.

ng generate component hero-app --inline-style
      
      ng generate component hero-app --inline-style
    

组件元数据中的样式文件

Style files in component metadata

你可以通过把外部 CSS 文件添加到 @ComponentstyleUrls 属性中来加载外部样式。

You can load styles from external CSS files by adding a styleUrls property to a component's @Component decorator:

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

注意:这些样式只对当前组件生效。 它们既不会作用于模板中嵌入的任何组件,也不会作用于投影进来的组件(如 ng-content )。

Reminder: the styles in the style file apply only to this component. They are not inherited by any components nested within the template nor by any content projected into the component.

你可以指定多个样式文件,甚至可以组合使用 stylestyleUrls 方式。

You can specify more than one styles file or even a combination of styles and styleUrls.

当你使用 Angular CLI 的 ng generate component命令但不带 --inline-style 标志时,CLI 会为你创建一个空白的样式表文件,并且在所生成组件的 styleUrls 中引用该文件。

When you use the Angular CLI command ng generate componentwithout the --inline-style flag, it creates an empty styles file for you and references that file in the component's generated styleUrls.

ng generate component hero-app
      
      ng generate component hero-app
    

模板内联样式

Template inline styles

你也可以直接在组件的 HTML 模板中写 <style> 标签来内嵌 CSS 样式。

You can embed CSS styles directly into the HTML template by putting them inside <style> tags.

@Component({ selector: 'app-hero-controls', template: ` <style> button { background-color: white; border: 1px solid #777; } </style> <h3>Controls</h3> <button (click)="activate()">Activate</button> ` })
src/app/hero-controls.component.ts
      
      
  1. @Component({
  2. selector: 'app-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> 标签。

You can also write <link> tags into the component's HTML template.

@Component({ selector: 'app-hero-team', template: ` <!-- We must use a relative URL so that the AOT compiler can find the stylesheet --> <link rel="stylesheet" href="../assets/hero-team.component.css"> <h3>Team</h3> <ul> <li *ngFor="let member of hero.team"> {{member}} </li> </ul>` })
src/app/hero-team.component.ts
      
      
  1. @Component({
  2. selector: 'app-hero-team',
  3. template: `
  4. <!-- We must use a relative URL so that the AOT compiler can find the stylesheet -->
  5. <link rel="stylesheet" href="../assets/hero-team.component.css">
  6. <h3>Team</h3>
  7. <ul>
  8. <li *ngFor="let member of hero.team">
  9. {{member}}
  10. </li>
  11. </ul>`
  12. })

当使用 CLI 进行构建时,要确保这个链接到的样式表文件被复制到了服务器上。参见 CLI 官方文档

When building with the CLI, be sure to include the linked style file among the assets to be copied to the server as described in the CLI wiki.

只要引用过,CLI 就会计入这个样式表,无论这个 link 标签的 href 指向的 URL 是相对于应用根目录的还是相对于组件文件的。

Once included, the CLI will include the stylesheet, whether the link tag's href URL is relative to the application root or the component file.

CSS @imports 语法

CSS @imports

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

You can also import CSS files into the CSS files using the standard CSS @import rule. For details, see @importon the MDN site.

这种情况下,URL 是相对于你正在导入的 CSS 文件的。

In this case, the URL is relative to the CSS file into which you're importing.

/* The AOT compiler needs the `./` to show that this is local */ @import './hero-details-box.css';
src/app/hero-details.component.css (excerpt)
      
      /* The AOT compiler needs the `./` to show that this is local */
@import './hero-details-box.css';
    

外部以及全局样式文件

External and global style files

当使用 CLI 进行构建时,你必须配置 angular.json 文件,使其包含所有外部资源(包括外部的样式表文件)。

When building with the CLI, you must configure the angular.json to include all external assets, including external style files.

在它的 styles 区注册这些全局样式文件,默认情况下,它会有一个预先配置的全局 styles.css 文件。

Register global style files in the styles section which, by default, is pre-configured with the global styles.css file.

要了解更多,参见 CLI 官方文档

See the CLI wiki to learn more.

非 CSS 样式文件

Non-CSS style files

如果使用 CLI 进行构建,那么你可以用 sasslessstylus 来编写样式,并使用相应的扩展名(.scss.less.styl)把它们指定到 @Component.styleUrls 元数据中。例子如下:

If you're building with the CLI, you can write style files in sass, less, or stylus and specify those files in the @Component.styleUrls metadata with the appropriate extensions (.scss, .less, .styl) as in the following example:

@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) ...
      
      @Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
...
    

CLI 的构建过程会运行相关的预处理器。

The CLI build process runs the pertinent CSS preprocessor.

当使用 ng generate component 命令生成组件文件时,CLI 会默认生成一个空白的 CSS 样式文件(.css)。 你可以配置 CLI,让它默认使用你喜欢的 CSS 预处理器,参见 CLI 官方文档 中的解释。

When generating a component file with ng generate component, the CLI emits an empty CSS styles file (.css) by default. You can configure the CLI to default to your preferred CSS preprocessor as explained in the CLI wiki.

添加到 @Component.styles 数组中的字符串必须写成 CSS,因为 CLI 没法对这些内联的样式使用任何 CSS 预处理器。

Style strings added to the @Component.styles array must be written in CSS because the CLI cannot apply a preprocessor to inline styles.

视图封装模式

View encapsulation

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

As discussed earlier, component CSS styles are encapsulated into the component's view and don't affect the rest of the application.

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

To control how this encapsulation happens on a per component basis, you can set the view encapsulation mode in the component metadata. Choose from the following modes:

  • ShadowDom 模式使用浏览器原生的 Shadow DOM 实现(参见 MDN 上的 Shadow DOM)来为组件的宿主元素附加一个 Shadow DOM。组件的视图被附加到这个 Shadow DOM 中,组件的样式也被包含在这个 Shadow DOM 中。(译注:不进不出,没有样式能进来,组件样式出不去。)

    ShadowDom view encapsulation uses the browser's native shadow DOM implementation (see Shadow DOM on the MDN site) to attach a shadow DOM to the component's host element, and then puts the component view inside that shadow DOM. The component's styles are included within the shadow DOM.

  • Native 视图包装模式使用浏览器原生 Shadow DOM 的一个废弃实现 —— 参见变化详情

    Native view encapsulation uses a now deprecated version of the browser's native shadow DOM implementation - learn about the changes.

  • Emulated 模式(默认值)通过预处理(并改名)CSS 代码来模拟 Shadow DOM 的行为,以达到把 CSS 样式局限在组件视图中的目的。 更多信息,见附录 1 。(译注:只进不出,全局样式能进来,组件样式出不去)

    Emulated view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing (and renaming) the CSS code to effectively scope the CSS to the component's view. For details, see Appendix 1.

  • None 意味着 Angular 不使用视图封装。 Angular 会把 CSS 添加到全局样式中。而不会应用上前面讨论过的那些作用域规则、隔离和保护等。 从本质上来说,这跟把组件的样式直接放进 HTML 是一样的。(译注:能进能出。)

    None means that Angular does no view encapsulation. Angular adds the CSS to the global styles. The scoping rules, isolations, and protections discussed earlier don't apply. This is essentially the same as pasting the component's styles into the HTML.

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

To set the components encapsulation mode, use the encapsulation property in the component metadata:

// warning: few browsers support shadow DOM encapsulation at this time encapsulation: ViewEncapsulation.Native
src/app/quest-summary.component.ts
      
      // warning: few browsers support shadow DOM encapsulation at this time
encapsulation: ViewEncapsulation.Native
    

ShadowDom 模式只适用于提供了原生 Shadow DOM 支持的浏览器(参见 Can I use 上的 Shadow DOM v1 部分)。 它仍然受到很多限制,这就是为什么仿真 (Emulated) 模式是默认选项,并建议将其用于大多数情况。

ShadowDom view encapsulation only works on browsers that have native support for shadow DOM (see Shadow DOM v1 on the Can I use site). The support is still limited, which is why Emulated view encapsulation is the default mode and recommended in most cases.

查看生成的 CSS

Inspecting generated CSS

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

When using emulated view encapsulation, Angular preprocesses all component styles so that they approximate the standard shadow CSS scoping rules.

在启用了仿真模式的 Angular 应用的 DOM 树中,每个 DOM 元素都被加上了一些额外的属性。

In the DOM of a running Angular application with emulated view encapsulation enabled, each DOM element has some extra attributes attached to it:

<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>
      
      <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>
    

生成出的属性分为两种:

There are two kinds of generated attributes:

  • 一个元素在原生封装方式下可能是 Shadow DOM 的宿主,在这里被自动添加上一个 _nghost 属性。 这是组件宿主元素的典型情况。

    An element that would be a shadow DOM host in native encapsulation has a generated _nghost attribute. This is typically the case for component host elements.

  • 组件视图中的每一个元素,都有一个 _ngcontent 属性,它会标记出该元素属于哪个宿主的模拟 Shadow DOM。

    An element within a component's view has a _ngcontent attribute that identifies to which host's emulated shadow DOM this element belongs.

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

The exact values of these attributes aren't important. They are automatically generated and you never refer to them in application code. But they are targeted by the generated component styles, which are in the <head> section of the DOM:

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

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

这些就是那些样式被处理后的结果,每个选择器都被增加了 _nghost_ngcontent 属性选择器。 这些额外的选择器实现了本文所描述的这些作用域规则。

These styles are post-processed so that each selector is augmented with _nghost or _ngcontent attribute selectors. These extra selectors enable the scoping rules described in this page.