Angular9 組件樣式

2020-07-01 14:33 更新

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

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

使用組件樣式

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

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

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

范圍化的樣式

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

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

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

  • 可以使用對每個組件最有意義的 CSS 類名和選擇器。

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

  • 組件的樣式不會因為別的地方修改了樣式而被意外改變。

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

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

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

特殊的選擇器

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

:host

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

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

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

:host 選擇是是把宿主元素作為目標的唯一方式。除此之外,你將沒辦法指定它, 因為宿主不是組件自身模板的一部分,而是父組件模板的一部分。

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

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

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

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

:host-context

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

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

在下面的例子中,只有當(dāng)某個祖先元素有 CSS 類 theme-light 時,才會把 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

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

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

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

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

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

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

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

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

把樣式加載進組件中

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

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

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

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

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

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

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

這個數(shù)組中的每一個字符串(通常也只有一個)定義一份 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 {
/* . . . */
}

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

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

ng generate component hero-app --inline-style

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

你可以通過把外部 CSS 文件添加到 @ComponentstyleUrls 屬性中來加載外部樣式。

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

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

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

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

ng generate component hero-app

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

你也可以直接在組件的 HTML 模板中寫 <style> 標簽來內(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 標簽

你也可以在組件的 HTML 模板中寫 <link> 標簽。

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 進行構(gòu)建時,要確保這個鏈接到的樣式表文件被復(fù)制到了服務(wù)器上。

  • 只要引用過,CLI 就會計入這個樣式表,無論這個 link 標簽的 href 指向的 URL 是相對于應(yīng)用根目錄的還是相對于組件文件的。

CSS @imports 語法

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

在這種情況下,URL 是相對于你正在導(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 進行構(gòu)建時,你必須配置 "angular.json" 文件,使其包含所有外部資源(包括外部的樣式表文件)。

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

非 CSS 樣式文件

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

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

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

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

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

視圖封裝模式

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

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

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

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

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

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

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

Path:"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) 模式是默認選項,并建議將其用于大多數(shù)情況。

查看生成的 CSS

當(dāng)使用默認的仿真模式時,Angular 會對組件的所有樣式進行預(yù)處理,讓它們模仿出標準的 Shadow CSS 作用域規(guī)則。

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

生成出的屬性分為兩種:

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

組件視圖中的每一個元素,都有一個 _ngcontent 屬性,它會標記出該元素屬于哪個宿主的模擬 Shadow DOM。

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

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


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

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號