iT邦幫忙

2025 iThome 鐵人賽

DAY 29
1
Modern Web

Angular 進階實務 30天系列 第 29

Day 29:Angular Library – 建立與共用元件

  • 分享至 

  • xImage
  •  

前言

在中大型前端專案中,常常會出現多個應用程式共存的情況。例如:公司可能同時開發前台官網、後台管理系統、行動端的管理工具等。這些應用雖然功能不同,但往往會用到許多重複的 UI 元件與服務。若每個專案都各自維護一份程式碼,不僅浪費時間,更會導致版本不一致、維護困難。

為了解決這個問題,Angular 提供了 Library(函式庫) 的機制。我們可以將共用的元件、服務、pipe 或工具抽取出來,形成可重複安裝與引用的套件。從 Angular 15 開始,官方引入了 Standalone Components,而在 Angular 17 已成為最佳實踐。這讓我們可以不用再依賴 NgModule,元件本身就能獨立使用,讓 Library 更簡潔、彈性更高。

本文將逐步介紹如何建立一個 Standalone Angular Library,涵蓋從初始化、開發、建置,到最後發布的流程,並分享如何利用 CSS 變數 來讓樣式更容易客製化。


Angular Library 樣式控制方法簡介

由於 Angular Library 的樣式控制大概有三種控制方向,下圖是我整理的表格,我這裡提供 Level 2 CSS變數覆蓋,因為比較易於展示跟測試,在實作上也有一定的實作彈性。

其他的部分的展示就...看看之後有沒有力氣繼續寫😂。

🎯 三種主要分類方向

Level 核心概念 決定時機 實現方式 適用場景
Level 1 完全封閉 寫死在元件庫,無法客製 ⚡ Build-time 硬編碼 SCSS/CSS 內部工具、快速原型
Level 2 變數覆蓋 提供 CSS 變數,允許覆蓋 🔥 Runtime var(--primary-color) + :root 一般專案、簡單客製
Level 3 主題系統 匯出完整主題架構 ⚡🔥 混合 匯出主題檔案,系統化管理 大型平台、設計系統

為什麼需要 Library?

1. 減少重工

在多專案環境下,重複撰寫相同元件(如按鈕、Modal、表單驗證邏輯)是一種浪費。透過 Library,可以只寫一次,其他應用直接使用。

2. 統一風格

設計規範往往要求 UI 一致。若按鈕樣式分散在各專案,任何小修改都要多處同步,錯誤率高。集中於 Library 後,修改一次即可同步到所有專案。

3. 可版本化

Library 與 npm 套件相同,可以進行版本控制。當新專案需要最新元件,而舊專案仍需維持穩定時,就能透過版本號管理,避免相互影響。

4. 更佳的維護性

將共用邏輯抽離後,維護人員可以專注在 Library 的品質,而應用程式則聚焦在業務邏輯,開發效率因此大幅提升。


初始化專案與建立 Library

首先建立一個工作空間(workspace),裡面可以包含多個應用程式與 Library。

# 建立新的工作空間,這裡先不產生應用程式
ng new my-workspace --create-application=false

cd my-workspace

# 建立一個名為 my-lib 的 Library
ng generate library my-lib

# 建立一個 demo 用的應用程式來測試
ng generate application demo-app

執行後,Angular CLI 會在 projects/ 目錄下產生一個 my-lib 資料夾。其結構包含:

my-workspace/                # Angular 工作空間根目錄
├── projects/                # 存放 Library 與 Application
│   ├── my-lib/              # Library (可共用的元件/服務)
│   │   ├── src/
│   │   │   └── lib/         # Library 主要程式碼 (元件 / pipe / service)
│   │   │       └── my-button/
│   │   │           ├── my-button.component.ts
│   │   │           ├── my-button.component.html
│   │   │           ├── my-button.component.css
│   │   │           └── my-button.component.spec.ts
│   │   ├── public-api.ts    # 控制 Library 對外公開的 API
│   │   ├── ng-package.json  # Library 打包設定
│   │   └── package.json     # Library 的套件資訊
│   │
│   └── demo-app/            # 測試用的 Angular Application
│       ├── src/
│       │   ├── app/
│       │   │   ├── app.component.ts
│       │   │   └── app.component.html
│       │   └── styles.css   # 全域樣式,可覆寫 Library 顏色變數
│       └── angular.json
│
├── angular.json             # Workspace 設定檔
├── package.json             # Workspace 的 npm 設定
└── tsconfig.json            # TypeScript 全域設定

建立 Standalone 共用元件

舉例來說,我們想在 Library 中建立一個客製化按鈕,並使用 Standalone:

ng generate component my-button --project=my-lib --standalone

生成後,程式碼如下:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'lib-my-button',
  standalone: true,
  template: `<button [class]="buttonClass"><ng-content></ng-content></button>`,
  styleUrls: ['./my-button.component.css']
})
export class MyButtonComponent {
  @Input() variant: 'primary' | 'secondary' = 'primary';

  get buttonClass() {
    return `btn btn-${this.variant}`;
  }
}

✅ 這裡的關鍵是 standalone: true,代表這個元件可以獨立使用,不需要 NgModule。


公開 API(public-api.ts)

由於不再有 NgModule,我們只需要 export 元件即可:

// projects/my-lib/src/public-api.ts
export * from './lib/my-button/my-button.component';

Variant 與 CSS 的搭配

元件內的 variant 屬性會輸出對應的 class,例如 btn btn-primarybtn btn-secondary。但若沒有 CSS,這些 class 不會生效,因此要在元件樣式內定義規則。

在 Library 定義預設樣式

/* projects/my-lib/src/lib/my-button/my-button.component.css */

.btn {
  padding: 0.5rem 1rem;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
  border: none;
  transition: background 0.3s ease;
}

/* 使用 CSS 變數,提供預設值 */
.btn-primary {
  background-color: var(--primary-color, #007bff);
  color: #fff;
}

.btn-secondary {
  background-color: var(--secondary-color, #6c757d);
  color: #fff;
}

這裡使用 CSS 變數--primary-color--secondary-color)。如果外部專案沒有定義,就會採用藍色和灰色作為預設。


Library 的本地測試

建立元件後,可以透過 demo-app 測試,運行 npm start 即可:

// demo-app/src/app/app.component.ts
import { Component } from '@angular/core';
import { MyButtonComponent } from 'my-lib';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MyButtonComponent],
  template: `
    <lib-my-button variant="primary">確認</lib-my-button>
    <lib-my-button variant="secondary">取消</lib-my-button>
  `
})
export class AppComponent {}

這裡不再需要 AppModule,因為整個應用也能以 Standalone 方式啟動,可以先看看引用的效果。


在安裝專案中統一替換顏色

如果我們安裝這個 Library,只要在應用的 全域樣式 裡覆寫變數即可:

/* demo-app/src/styles.css */
:root {
  --primary-color: #ff5722;   /* 橘色 */
  --secondary-color: #4caf50; /* 綠色 */
}

這樣一來,所有 variant="primary" 的按鈕就會變成橘色,而 variant="secondary" 則會變成綠色。

https://ithelp.ithome.com.tw/upload/images/20250909/20162350DbURmptGVM.png

👉 優點:

  • 修改一次,全專案同步更新。
  • 不需要修改 Library 原始碼。
  • 可以準備多份配色檔,輕鬆支援 Light/Dark 模式或不同品牌主題。

建置 Library

功能完成後,可以透過 Angular CLI 進行建置:

ng build my-lib

建置成功的畫面
https://ithelp.ithome.com.tw/upload/images/20250909/20162350LyIPcwJmGH.png
建置完成後,會在 dist/my-lib/ 目錄產生編譯檔案。這些檔案就是之後可發布的內容。


設定套件資訊

projects/my-lib/package.json 中,可以設定套件名稱、版本與相依性,例如:

{
  "name": "@my-company/my-lib",
  "version": "1.0.0",
  "peerDependencies": {
    "@angular/common": "^17.0.0",
    "@angular/core": "^17.0.0"
  }
}

這裡的 peerDependencies 表示安裝此 Library 的專案必須同時具備對應版本的 Angular,避免衝突。


發布與使用

完成建置後,就能將 Library 發布到 npm 或公司內部 registry:

除非你要公開發佈,不然請記得加上 --registry--dry-run 測試
如何在私人的伺服器上安裝,會在下一篇介紹

cd dist/my-lib
npm publish   # ⚠️ 沒有指定 registry 時,會直接發佈到 npm 公開 registry!

# 測試或私人環境
npm publish --registry=http://your-private-registry.com/
npm publish --dry-run

在其他專案中安裝後,只需直接 import Standalone 元件:

import { MyButtonComponent } from '@my-company/my-lib';

即可在任何 component 的 imports 中使用。


Library 的實務應用

除了 UI 元件,還有許多場景適合放進 Library:

  1. 共用的 Service:API 呼叫、驗證機制、錯誤處理邏輯。
  2. Utility 函式:日期處理、字串轉換、金額格式化。
  3. 型別定義與介面:前後端共用的 DTO 或介面,避免重複定義。
  4. 管道(Pipe):例如時間格式化、貨幣轉換。

實務上的挑戰

雖然 Library 帶來許多好處,但也有需要注意的地方:

  • 版本管理複雜:多專案共用時,升級需謹慎。
  • 測試覆蓋率要求高:Library 屬於基礎建設,任何 bug 都會放大影響。
  • 文件不可或缺:若沒有清楚的 API 文件,開發者可能難以上手。

結語

Angular 17 已全面擁抱 Standalone Components,讓元件與服務能夠直接被引用,不再需要 NgModule,讓 Library 更乾淨、直觀。

透過合理的設計與版本控制,可以大幅減少重複程式碼,統一 UI 與邏輯,並在不同應用間保持一致性。再搭配 CSS 變數,能讓 Library 的使用者客製化配色,做到「定義一次 → 全專案統一替換」。


上一篇
Day 28:淺談 Monorepo
下一篇
Day 30:版本管理及Verdaccio
系列文
Angular 進階實務 30天31
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言