在日常工作中,當我們定義一個Component的時候,要考慮它的encapsulation封裝性,也就是說你期望這個元件裡定義的樣式是只作用於這個元件,還是想作用於全局。在Angular 中,元件的樣式可以封裝在元件的宿主元素中,這樣它們就不會影響應用程式的其餘部分。 Component 的裝飾器提供了encapsulation 選項,可用於控制如何基於每個元件套用視圖封裝。 【相關教學推薦:《angular教學》】
Angular中有三種封裝模式,分別是ViewEncapsulation.ShadowDom,ViewEncapsulation.Emulated,ViewEncapsulation.None。
export enum ViewEncapsulation { /** * Emulates a native Shadow DOM encapsulation behavior by adding a specific attribute to the * component's host element and applying the same attribute to all the CSS selectors provided * via {@link Component#styles styles} or {@link Component#styleUrls styleUrls}. * * This is the default option. */ Emulated = 0, /** * Doesn't provide any sort of CSS style encapsulation, meaning that all the styles provided * via {@link Component#styles styles} or {@link Component#styleUrls styleUrls} are applicable * to any HTML element of the application regardless of their host Component. */ None = 2, /** * Uses the browser's native Shadow DOM API to encapsulate CSS styles, meaning that it creates * a ShadowRoot for the component's host element which is then used to encapsulate * all the Component's styling. */ ShadowDom = 3 }
如果沒有提供,該值就會從CompilerOptions 中取得它。預設的編譯器選項是ViewEncapsulation.Emulated。
如果該策略設定為ViewEncapsulation.Emulated,且該元件沒有指定styles 或styleUrls,就會自動切換到ViewEncapsulation.None。
有沒有發現枚舉類型了為什麼沒有1 ?這個待會再說。
拋開Angular中的ShadowDom的封裝,先來看看什麼是ShadowDOM吧。
Shadow DOM 允許將隱藏的DOM 樹附加到常規的DOM 樹中——它以shadow root 節點為起始根節點,在這個根節點的下方,可以是任意元素,和普通的DOM 元素一樣。
這裡,有一些Shadow DOM 特有的術語需要我們了解:
你可以用同樣的方式來操作Shadow DOM,就跟操作常規DOM 一樣-例如新增子節點、設定屬性,以及為節點加入自己的樣式(例如透過element.style 屬性),或是為整個Shadow DOM 新增樣式(例如在元素內添加樣式)。不同的是,Shadow DOM 內部的元素始終不會影響到它外部的元素(除了:focus-within),這為封裝提供了便利。
讓我們來看一個簡單的例子吧。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Shadow DOM</title> <style> span{ color: green; } </style> </head> <body> <span>我是Root</span> <div id="app"></div> <script> let app = document.querySelector('#app'); let shadow1 = app.attachShadow({ mode: 'open'}); let style1 = document.createElement('style'); style1.appendChild(document.createTextNode("span{color: red;}")); shadow1.appendChild(style1); let span1 = document.createElement('span'); span1.textContent = 'I am span.'; shadow1.appendChild(span1); </script> </body> </html>
上面的例子,定義了全局span的樣式,也定義了shadowDOM裡的span樣式,可以看出相互不受影響。
了解了什麼是ShadowDOM後,再來看看Angular中ShadowDOM的封裝。
Angular 使用瀏覽器內建的Shadow DOM API 將元件的視圖包含在ShadowRoot(用作元件的宿主元素)中,並以隔離的方式套用所提供的樣式。 ViewEncapsulation.ShadowDom 僅適用於內建支援shadow DOM 的瀏覽器。並非所有瀏覽器都支援它,這就是為什麼ViewEncapsulation.Emulated 是推薦和預設模式的原因。
例如下面的這個例子,使用ViewEncapsulation.ShadowDom 。
@Component({ selector: 'user-child', templateUrl: 'UserChild.component.html', styles: [` h3{ color: red; } `], encapsulation: ViewEncapsulation.ShadowDom }) export class UserChildComponent implements OnInit { ..... }
從運行的頁面上看到,user-child元件內部被封裝成了一個ShadowDOM,style也被封裝在了裡面,並不會對外部的樣式造成影響。
Angular 會修改元件的CSS 選擇器,使它們只應用於元件的視圖,不影響應用程式中的其他元素(模擬Shadow DOM 行為)。
使用模擬視圖封裝時,Angular 會預處理所有元件的樣式,以便它們僅套用於元件的視圖。在正運行的Angular 應用程式的DOM 中,使用模擬視圖封裝模式的元件所在的元素附加了一些額外的屬性:
<hero-details _nghost-pmm-5> <h3 _ngcontent-pmm-5>Mister Fantastic</h3> <hero-team _ngcontent-pmm-5 _nghost-pmm-6> <h4 _ngcontent-pmm-6>Team</h4> </hero-team> </hero-details>
有兩種這樣的屬性:
屬性 | 詳情 |
---|---|
_nghost | 被加入到包裹元件視圖的元素中,這將是本機Shadow DOM 封裝中的ShadowRoots。組件的宿主元素通常就是這種情況。 |
_ngcontent | 被加入到元件檢視中的子元素上,這些屬性用於將元素與其各自模擬的ShadowRoots(具有符合_nghost 屬性的宿主元素)相符。 |
這些屬性的確切值是Angular 的私有實作細節。它們是自動生成的,你不應該在應用程式程式碼中引用它們。
它們以產生的元件樣式為目標,這些樣式會被注入到DOM 的部分:
[_nghost-pmm-5] { display: block; border: 1px solid black; } h4[_ngcontent-pmm-6] { background-color: white; border: 1px solid #777; }
這些樣式經過後製,以便每個CSS 選擇器都使用適當的_nghost 或_ngcontent 屬性進行擴充。這些修改後的選擇器可以確保樣式以隔離和有針對性的方式套用於元件的視圖。
<p>child works!</p>
p{ color: green; }
@Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.scss'], encapsulation: ViewEncapsulation.Emulated }) export class ChildComponent implements OnInit { ..... }
ViewEncapsulation.Emulated 設定的結果是沒有Shadow DOM,但透過Angular 提供的樣式包裝機制來封裝元件,使得元件的樣式不受外部影響。雖然樣式仍然是應用到整個document,但Angular 為p創建了一個[_ngcontent-oow-c11] 選擇器。可以看出,我們為元件定義的樣式,被Angular 修改了。簡單來說,儘管是也是全域樣式,但是由於自動選擇器的原因,並不會影響其他元件的樣式。如果手動在其他元素上也新增這個屬性,則樣式也會套用到這元素上。
Angular 不應用任何形式的視圖封裝,這意味著為元件指定的任何樣式實際上都是全域應用的,並且可以影響應用程式中存在的任何HTML 元素。這種模式本質上與將樣式包含在HTML 本身中是一樣的。
parent:
<p #caption>parent works!{{count}}</p> <p #caption>第一個:{{count}}</p> <span class="red-font">parent</span> <app-child></app-child>
child:
<div style="border: 1px solid green;"> <p>child works!</p> <span class="red-font">Child</span> </div>
p{ color: green; } .red-font { color: red; }
@Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.scss'], encapsulation: ViewEncapsulation.None }) export class ChildComponent implements OnInit { ..... }
在Angular2中使用ViewEncapsulation.Native。
@Component({ ..., encapsulation: ViewEncapsulation.Native }) export class UserComponent {
ViewEncapsulation.Native 設定的結果是使用原生的Shadow DOM 特性。 Angular 會把元件依照瀏覽器支援的Shadow DOM 形式渲染。其實這種就是後來的ViewEncapsulation.ShadowDom。
我們介紹了Angular視圖封裝的三種方式,各自的特點,日常工作中要根據特定的場景去選擇哪種封裝方式。