選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
8 分鐘閱讀時間 (1549 個字)

Angular #21 - 深入 Components [4]

shutterstock_198004562
  • 目前為止都還沒仔細談過 Component 的樣式

    • Components 在 設計上 就是要針對狀態自我管理,這其中也包含樣式
    • 如果你是用過其他早期前端框架,或是早期的 Server Side Rendering 如 JSP, Razor, 通常都會有一個全域的 style 設定檔當作基底
      • 僅管如此,Component 是可以在不跟 global style 衝突的情況下定義自己的 style 的,這相當方便,不用再害怕自己的樣式去破壞到別支功能的版面
      • 替 Component 加上樣式有很多種方法,但…為保求一致性,建議選擇統一一種方式就好,否則這邊用 A 方法,那邊用 B 方法,會很混亂
  • 加上 Style 的方法

    • 全域
      • index.html 中透過 link href 的方式引用
      • 承上,也可以在 angular.json(p.s. Angular 6 之前叫 .angular-cli.json) 中加入參考,如下圖的 styles 陣列
        • 因為有在 packages.json 中加入 bootstrap,所以 node_modules 中會在 npm install 時載入對應的資源檔
        • 承上,因此我們可以在 angular.json 中加入 bootstrap 的樣式,這樣不管在哪個 Component 都可以吃到 Bootstrap 的樣式
    • 單一 Component
      • Inline
        • 這是 HTML 就有的方法,並不限定於 Angular 應用程式
        • 用法就是直接在 component 的 template 中的元素加上 style
            <button type="button">
                <p style="color:red;">Press me</p>
            </button>
      • Component Linked CSS
        • @Component 這個 decorator 有一個選項是 styleUrls,在這個陣列中引用的檔案都會被連結到此 Component
        • 承上,Angular 會把那些樣式匯整成一個單一的 <style>
      • Component inline CSS
        • 直接加樣式規則到 styles 這個屬性(一樣在 Component Decorator)
        • 同上,Angular 會匯整到單一的 <style>
  • 上面這麼多方法,光看就眼花了,來看一個實際的例子:

    • 我們會針對 Reload 按鈕的樣式(背景色)作調整,來看上面的機制是怎麼運作的:

      • 首先,先在 navbar.component.css 加上以下的 CSS

          .btn { background-color: #e32738; }
        • 別忘了這個檔案是放在 @Component 的 styleUrls 陣 列中的
        • 這麼設定就會覆寫 global CSS 中 bootstrap 給予按鈕預設的顏色,會變成我們所設定的紅色(原本是綠色)
        • 特別留意其他的 Component 如 Nodes-Row 中的 button 顏色並沒有異動,由此可見 Component 本身的樣式異動是排它
      • 接著我們調整 Navbar @Component 的 metadata,加上 styles 如下:

          import { Component, Output, EventEmitter } from '@angular/core';
        
          @Component({
            selector: 'app-navbar',
            templateUrl: './navbar.component.html',
            styleUrls: ['./navbar.component.css'],
            styles: [`.btn { background-color: #999999; }`]
          })
          export class NavbarComponent  {
            constructor() { }
            @Output() onRefresh: EventEmitter<null> = new EventEmitter<null>();
        
            refresh() {
              this.onRefresh.emit();
            }
          }
        • 這時候你再看 Reload 按鈕,它變成灰色的了!
        • 承上,換句話說 styles 會覆蓋所有 styleUrls 裡重覆的樣式(看似如此),不過實際上是誰最後宣告,誰就是贏家(沒事別兩個都定義)
      • 還沒結束呢,我們接著打開 navbar.component.html,並加入以下:

          <style>.btn { background-color: #3274b8; }</style>
        • 這時候你再看 Reload 按鈕,它變成藍色的了!
        • 這就是所謂的 inline style(不過是在 template)
      • 最後最後,如果直接在 button 上加 style 如下:

          <button class="btn btn-success" 
                  type="button" 
                  style="background-color:#8616f6" 
                  (click)="refresh()">Reload
          </button>
        • 按鈕變成紫色的了!
  • 所以說,統一樣式的設定方式是很重要的

    • 我個人認為原則上方向就是
      • 先以 styleUrls 為主
      • 如果真的可以共用再抽到 global(但其實想不太到會有這種場景,除非整個系統的 Theme 都是某個 UI,像是 bootstrap)
  • Encapsulation modes(封裝模式)

    • 上面介紹了那麼多種調整樣式的方式,但最後它會如何作用在 HTML 上,其實是看封裝模式是設定成哪種來決定,在 Angular 共有三種:

      • None
        • 在這個模式下,所有的 styleUrls/styles/template inline 全部都會被抬升到 index.html 的 <head> 區塊,但因為有不同的 Component,所以渲染的先後順序會造成最後的結果不同,換句話說,等著衝突吧!
        • 你有可能會因為以下理由使用或避免這個模式:
          • Styles bleed out(樣式會滲透出去): 有時候你的前端應用程式就不需要封裝,大家共用同一組 CSS 就好,此時 Component 內的 styleUrls/styles 應該都會是空的
          • Global styles bleed in(全域樣式會滲透進來): 所有的 Component 就共用全域的 CSS
          • Templates are unmodified(Template 不動如山): 在 Template 寫什麼就是什麼,不會再作進一步的轉換,特別留意正因為這個特性,:host 或是 ::shadow 都不會有任何作用(因為Shadow DOM 不存在)
      • Emulated
        • 預設 Angular 就會走這個模式,在渲染的時候 Angular 會以它特有的演算法轉換樣式,給予一個獨特的樣式識別證(e.g. 獨特的 class name)。正因為是模擬的,所以才會是以這種方式呈現
        • 你有可能會因為以下理由使用或避免這個模式:
          • Styles are isolated(邊緣人):因為命名獨特,所以不會跟外面的世界衝突,就是不會跟全域的樣式相衝
          • Styles bleed in(全域樣式滲入):自行封裝的樣式不會影響到外面的世界,但全域樣式還是可以拿來用
          • Unique selectors(獨特的..): DOM 上會有特殊的 attribute(e.g. _ngcontent-ofq-3),意思是說如果你想用全域的樣式,selector 要寫好寫滿(包含獨特的部份)
      • Native
        • 最狂的就是這個了,樣式都會作用在 Shadow DOM,與世隔絕
        • 你有可能會因為以下理由使用或避免這個模式:
          • Uses Shadow DOM: 如果這個不是封裝,什麼才叫封裝?
          • Parent and siblings styles bleed in: Angular 團隊主張巢狀的 Component 要可以共用樣式,因此…Child 也許會受 Parent 影響(這可能不是你想要的)
          • Limited support: 最潮最新的瀏覽器才支援,縱使有 shadow DOM polyfill,還是有可能會出包(IE 去死),在用之前建議上 MDN 看一下它的支援度 => 這裡
    • 所以說那個醬汁呢?

      • 模式要設定在哪裡?其實又是我們的老朋友 @Component,其中有一個屬性叫 encapsulation,記得要 import ViewEncapsulation

      • 以 MetricComponent 為例,假設我任性想沿用全域的樣式就好,可以如下:

          import { ChangeDetectionStrategy, Component, Input, OnChanges, ViewEncapsulation } from '@angular/core';
        
          @Component({
            selector: 'app-metric',
            templateUrl: './metric.component.html',
            styleUrls: ['./metric.component.css'],
            changeDetection: ChangeDetectionStrategy.OnPush,
            encapsulation: ViewEncapsulation.None
          })
          export class MetricComponent implements OnChanges {
            @Input('used') value: number = 0;
            @Input('available') max: number = 100;
        
            ngOnChanges(changes) {
              if(changes.value && isNaN(changes.value.currentValue)) this.value = 0;
              if(changes.max && isNaN(changes.max.currentValue)) this.max = 100;
            }
        
            isDanger() {
              return this.value / this.max > 0.7;
            }
          }
  • 小結:

    • 沒有小傑,休刊了
      • 下一篇會提到一個很常用又重要的東西,動態渲染
      • 這篇主要是想表達樣式的封裝也是很重要的
Angular #22 - 深入 Components [5]
Angular #20 - 深入 Components [3]

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
Guest
2025/05/22, 週四

Captcha 圖像