Angular9 組件樣式

2020-07-01 14:33 更新

Angular 應(yīng)用使用標(biāo)準(zhǔn)的 CSS 來(lái)設(shè)置樣式。這意味著你可以把關(guān)于 CSS 的那些知識(shí)和技能直接用于 Angular 程序中,例如:樣式表、選擇器、規(guī)則以及媒體查詢等。

另外,Angular 還能把組件樣式捆綁在組件上,以實(shí)現(xiàn)比標(biāo)準(zhǔn)樣式表更加模塊化的設(shè)計(jì)。

使用組件樣式

對(duì)你編寫(xiě)的每個(gè) Angular 組件來(lái)說(shuō),除了定義 HTML 模板之外,還要定義用于模板的 CSS 樣式、 指定任意的選擇器、規(guī)則和媒體查詢。

實(shí)現(xiàn)方式之一,是在組件的元數(shù)據(jù)中設(shè)置 styles 屬性。 styles 屬性可以接受一個(gè)包含 CSS 代碼的字符串?dāng)?shù)組。 通常你只給它一個(gè)字符串就行了,如同下例:

Path:"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 {
/* . . . */
}

范圍化的樣式

它們既不會(huì)被模板中嵌入的組件繼承,也不會(huì)被通過(guò)內(nèi)容投影(如 ng-content)嵌進(jìn)來(lái)的組件繼承。

在這個(gè)例子中,h1 的樣式只對(duì) HeroAppComponent 生效,既不會(huì)作用于內(nèi)嵌的 HeroMainComponent,也不會(huì)作用于應(yīng)用中其它任何地方的 <h1> 標(biāo)簽。

這種范圍限制就是所謂的樣式模塊化特性

  • 可以使用對(duì)每個(gè)組件最有意義的 CSS 類名和選擇器。

  • 類名和選擇器是局限于該組件的,它不會(huì)和應(yīng)用中其它地方的類名和選擇器沖突。

  • 組件的樣式不會(huì)因?yàn)閯e的地方修改了樣式而被意外改變。

  • 你可以讓每個(gè)組件的 CSS 代碼和它的 TypeScript、HTML 代碼放在一起,這將促成清爽整潔的項(xiàng)目結(jié)構(gòu)。

  • 將來(lái)你可以修改或移除組件的 CSS 代碼,而不用遍歷整個(gè)應(yīng)用來(lái)看它有沒(méi)有在別處用到。

注:
- 在 @Component 的元數(shù)據(jù)中指定的樣式只會(huì)對(duì)該組件的模板生效。

特殊的選擇器

組件樣式中有一些從影子(Shadow) DOM 樣式范圍領(lǐng)域引入的特殊選擇器:

:host

使用 :host 偽類選擇器,用來(lái)選擇組件宿主元素中的元素(相對(duì)于組件模板內(nèi)部的元素)。

Path:"src/app/hero-details.component.css" 。

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

:host 選擇是是把宿主元素作為目標(biāo)的唯一方式。除此之外,你將沒(méi)辦法指定它, 因?yàn)樗拗鞑皇墙M件自身模板的一部分,而是父組件模板的一部分。

要把宿主樣式作為條件,就要像函數(shù)一樣把其它選擇器放在 :host 后面的括號(hào)中。

下一個(gè)例子再次把宿主元素作為目標(biāo),但是只有當(dāng)它同時(shí)帶有 active CSS 類的時(shí)候才會(huì)生效。

Path:"src/app/hero-details.component.css" 。

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

:host-context

有時(shí)候,基于某些來(lái)自組件視圖外部的條件應(yīng)用樣式是很有用的。 例如,在文檔的 <body> 元素上可能有一個(gè)用于表示樣式主題 (theme) 的 CSS 類,你應(yīng)當(dāng)基于它來(lái)決定組件的樣式。

這時(shí)可以使用 :host-context() 偽類選擇器。它也以類似 :host() 形式使用。它在當(dāng)前組件宿主元素的祖先節(jié)點(diǎn)中查找 CSS 類, 直到文檔的根節(jié)點(diǎn)為止。在與其它選擇器組合使用時(shí),它非常有用。

在下面的例子中,只有當(dāng)某個(gè)祖先元素有 CSS 類 theme-light 時(shí),才會(huì)把 background-color 樣式應(yīng)用到組件內(nèi)部的所有 <h2> 元素中。

Path:"src/app/hero-details.component.css" 。

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

已廢棄 /deep/、>>> 和 ::ng-deep

組件樣式通常只會(huì)作用于組件自身的 HTML 上。

把偽類 ::ng-deep 應(yīng)用到任何一條 CSS 規(guī)則上就會(huì)完全禁止對(duì)那條規(guī)則的視圖包裝。任何帶有 ::ng-deep 的樣式都會(huì)變成全局樣式。為了把指定的樣式限定在當(dāng)前組件及其下級(jí)組件中,請(qǐng)確保在 ::ng-deep 之前帶上 :host 選擇器。如果 ::ng-deep 組合器在 :host 偽類之外使用,該樣式就會(huì)污染其它組件。

這個(gè)例子以所有的 <h3> 元素為目標(biāo),從宿主元素到當(dāng)前元素再到 DOM 中的所有子元素:

Path:"src/app/hero-details.component.css" 。

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

/deep/ 組合器還有兩個(gè)別名:>>>::ng-deep。

注:
- /deep/>>> 選擇器只能被用在仿真 (emulated) 模式下。 這種方式是默認(rèn)值,也是用得最多的方式。

  • CSS 標(biāo)準(zhǔn)中用于 "刺穿 Shadow DOM" 的組合器已經(jīng)被廢棄,并將這個(gè)特性從主流瀏覽器和工具中移除。 因此,Angular 也將會(huì)移除對(duì)它們的支持(包括 /deep/、&&&::ng-deep)。 目前,建議先統(tǒng)一使用 ::ng-deep,以便兼容將來(lái)的工具。

把樣式加載進(jìn)組件中

有幾種方式把樣式加入組件:

  • 設(shè)置 stylesstyleUrls 元數(shù)據(jù)

  • 內(nèi)聯(lián)在模板的 HTML 中

  • 通過(guò) CSS 文件導(dǎo)入

上述作用域規(guī)則對(duì)所有這些加載模式都適用。

元數(shù)據(jù)中的樣式

你可以給 @Component 裝飾器添加一個(gè) styles 數(shù)組型屬性。

這個(gè)數(shù)組中的每一個(gè)字符串(通常也只有一個(gè))定義一份 CSS。

Path:"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 {
/* . . . */
}

注:
- 這些樣式只對(duì)當(dāng)前組件生效。 它們既不會(huì)作用于模板中嵌入的任何組件,也不會(huì)作用于投影進(jìn)來(lái)的組件(如 ng-content )。

當(dāng)使用 --inline-styles 標(biāo)識(shí)創(chuàng)建組件時(shí),Angular CLI 的 ng generate component 命令就會(huì)定義一個(gè)空的 styles 數(shù)組。

ng generate component hero-app --inline-style

組件元數(shù)據(jù)中的樣式文件

你可以通過(guò)把外部 CSS 文件添加到 @ComponentstyleUrls 屬性中來(lái)加載外部樣式。

  1. Path:"src/app/hero-app.component.ts (CSS in file)" 。

    @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 {
    /* . . . */
    }

  1. Path:"src/app/hero-app.component.css" 。

    h1 {
      font-weight: normal;
    }

注:
- 這些樣式只對(duì)當(dāng)前組件生效。 它們既不會(huì)作用于模板中嵌入的任何組件,也不會(huì)作用于投影進(jìn)來(lái)的組件(如 ng-content )。

  • 你可以指定多個(gè)樣式文件,甚至可以組合使用 stylestyleUrls 方式。

當(dāng)你使用 Angular CLI 的 ng generate component 命令但不帶 --inline-style 標(biāo)志時(shí),CLI 會(huì)為你創(chuàng)建一個(gè)空白的樣式表文件,并且在所生成組件的 styleUrls 中引用該文件。

ng generate component hero-app

模板內(nèi)聯(lián)樣式

你也可以直接在組件的 HTML 模板中寫(xiě) <style> 標(biāo)簽來(lái)內(nèi)嵌 CSS 樣式。

Path:"src/app/hero-controls.component.ts" 。

@Component({
  selector: 'app-hero-controls',
  template: `
    <style>
      button {
        background-color: white;
        border: 1px solid #777;
      }
    </style>
    <h3>Controls</h3>
    <button (click)="activate()">Activate</button>
  `
})

模板中的 link 標(biāo)簽

你也可以在組件的 HTML 模板中寫(xiě) <link> 標(biāo)簽。

Path:"src/app/hero-team.component.ts" 。

@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>`
})

注:
- 當(dāng)使用 CLI 進(jìn)行構(gòu)建時(shí),要確保這個(gè)鏈接到的樣式表文件被復(fù)制到了服務(wù)器上。

  • 只要引用過(guò),CLI 就會(huì)計(jì)入這個(gè)樣式表,無(wú)論這個(gè) link 標(biāo)簽的 href 指向的 URL 是相對(duì)于應(yīng)用根目錄的還是相對(duì)于組件文件的。

CSS @imports 語(yǔ)法

你還可以利用標(biāo)準(zhǔn)的 CSS @import 規(guī)則來(lái)把其它 CSS 文件導(dǎo)入到 CSS 文件中。

在這種情況下,URL 是相對(duì)于你正在導(dǎo)入的 CSS 文件的。

Path:"src/app/hero-details.component.css (excerpt)" 。

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

外部以及全局樣式文件

當(dāng)使用 CLI 進(jìn)行構(gòu)建時(shí),你必須配置 "angular.json" 文件,使其包含所有外部資源(包括外部的樣式表文件)。

在它的 styles 區(qū)注冊(cè)這些全局樣式文件,默認(rèn)情況下,它會(huì)有一個(gè)預(yù)先配置的全局 "styles.css" 文件。

非 CSS 樣式文件

如果使用 CLI 進(jìn)行構(gòu)建,那么你可以用 sass、lessstylus 來(lái)編寫(xiě)樣式,并使用相應(yīng)的擴(kuò)展名(.scss.less、.styl)把它們指定到 @Component.styleUrls 元數(shù)據(jù)中。例子如下:

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

CLI 的構(gòu)建過(guò)程會(huì)運(yùn)行相關(guān)的預(yù)處理器。

當(dāng)使用 ng generate component 命令生成組件文件時(shí),CLI 會(huì)默認(rèn)生成一個(gè)空白的 CSS 樣式文件(.css)。 你可以配置 CLI,讓它默認(rèn)使用你喜歡的 CSS 預(yù)處理器。

注:
- 添加到 @Component.styles 數(shù)組中的字符串必須寫(xiě)成 CSS,因?yàn)?CLI 沒(méi)法對(duì)這些內(nèi)聯(lián)的樣式使用任何 CSS 預(yù)處理器。

視圖封裝模式

像上面討論過(guò)的一樣,組件的 CSS 樣式被封裝進(jìn)了自己的視圖中,而不會(huì)影響到應(yīng)用程序的其它部分。

通過(guò)在組件的元數(shù)據(jù)上設(shè)置視圖封裝模式,你可以分別控制每個(gè)組件的封裝模式。 可選的封裝模式一共有如下幾種:

  • ShadowDom 模式使用瀏覽器原生的 Shadow DOM 實(shí)現(xiàn)(參見(jiàn) MDN 上的 Shadow DOM)來(lái)為組件的宿主元素附加一個(gè) Shadow DOM。組件的視圖被附加到這個(gè) Shadow DOM 中,組件的樣式也被包含在這個(gè) Shadow DOM 中。(譯注:不進(jìn)不出,沒(méi)有樣式能進(jìn)來(lái),組件樣式出不去。)

  • Native 視圖包裝模式使用瀏覽器原生 Shadow DOM 的一個(gè)廢棄實(shí)現(xiàn) —— 參見(jiàn)變化詳情。

  • Emulated 模式(默認(rèn)值)通過(guò)預(yù)處理(并改名)CSS 代碼來(lái)模擬 Shadow DOM 的行為,以達(dá)到把 CSS 樣式局限在組件視圖中的目的。 更多信息,見(jiàn)附錄 1。(譯注:只進(jìn)不出,全局樣式能進(jìn)來(lái),組件樣式出不去)

  • None 意味著 Angular 不使用視圖封裝。 Angular 會(huì)把 CSS 添加到全局樣式中。而不會(huì)應(yīng)用上前面討論過(guò)的那些作用域規(guī)則、隔離和保護(hù)等。 從本質(zhì)上來(lái)說(shuō),這跟把組件的樣式直接放進(jìn) HTML 是一樣的。(譯注:能進(jìn)能出。)

通過(guò)組件元數(shù)據(jù)中的 encapsulation 屬性來(lái)設(shè)置組件封裝模式:

Path:"src/app/quest-summary.component.ts" 。

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

ShadowDom 模式只適用于提供了原生 Shadow DOM 支持的瀏覽器(參見(jiàn) Can I use 上的 Shadow DOM v1 部分)。 它仍然受到很多限制,這就是為什么仿真 (Emulated) 模式是默認(rèn)選項(xiàng),并建議將其用于大多數(shù)情況。

查看生成的 CSS

當(dāng)使用默認(rèn)的仿真模式時(shí),Angular 會(huì)對(duì)組件的所有樣式進(jìn)行預(yù)處理,讓它們模仿出標(biāo)準(zhǔn)的 Shadow CSS 作用域規(guī)則。

在啟用了仿真模式的 Angular 應(yīng)用的 DOM 樹(shù)中,每個(gè) 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>

生成出的屬性分為兩種:

一個(gè)元素在原生封裝方式下可能是 Shadow DOM 的宿主,在這里被自動(dòng)添加上一個(gè) _nghost 屬性。 這是組件宿主元素的典型情況。

組件視圖中的每一個(gè)元素,都有一個(gè) _ngcontent 屬性,它會(huì)標(biāo)記出該元素屬于哪個(gè)宿主的模擬 Shadow DOM。

這些屬性的具體值并不重要。它們是自動(dòng)生成的,并且你永遠(yuǎn)不會(huì)在程序代碼中直接引用到它們。 但它們會(huì)作為生成的組件樣式的目標(biāo),就像 DOM 的 <head> 中一樣:

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


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

這些就是那些樣式被處理后的結(jié)果,每個(gè)選擇器都被增加了 _nghost_ngcontent 屬性選擇器。 這些額外的選擇器實(shí)現(xiàn)了本文所描述的這些作用域規(guī)則。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)