☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
angular 中級

💉 Angular 服務與依賴注入

📌 什麼是服務(Service)?

服務是一個專注於特定功能的 TypeScript 類別,用來處理:

  • 呼叫 API 取得資料
  • 商業邏輯運算
  • 跨元件共享資料
  • 日誌紀錄等

💡 比喻:元件是「店員」(負責和顧客互動),服務是「倉庫管理員」(負責管理資料)。 店員不需要知道倉庫怎麼運作,只要跟倉庫管理員要東西就好。

// user.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'  // 整個應用共享同一個實例(Singleton)
})
export class UserService {
  private users: User[] = [];

  getUsers(): User[] {
    return this.users;
  }

  addUser(user: User): void {
    this.users.push(user);
  }
}

📌 依賴注入(Dependency Injection, DI)

Angular 的 DI 和 ASP.NET Core 的 DI 概念完全相同

對比 ASP.NET Core DI

// ASP.NET Core — 在 Program.cs 註冊服務
builder.Services.AddScoped<IUserService, UserService>();

// Controller 透過建構子注入
public class UserController : Controller
{
    private readonly IUserService _userService;
    public UserController(IUserService userService)
    {
        _userService = userService;  // 框架自動注入
    }
}
// Angular — 在 @Injectable 裝飾器中註冊
@Injectable({
  providedIn: 'root'  // 相當於 ASP.NET Core 的 AddSingleton
})
export class UserService { /* ... */ }

// Component 透過建構子注入
@Component({ /* ... */ })
export class UserListComponent {
  constructor(private userService: UserService) {
    // Angular 框架自動注入,和 ASP.NET Core 一樣!
  }
}

🎯 關鍵相似處

  • 兩者都是框架自動管理物件的建立和生命週期
  • 兩者都透過建構子注入
  • 兩者都支援不同的生命週期(Singleton / Scoped / Transient)

Angular DI 的生命週期

// Singleton(全應用共享,等同 ASP.NET Core AddSingleton)
@Injectable({ providedIn: 'root' })
export class GlobalService { }

// 元件層級(每個元件實例都有自己的一份,類似 AddScoped)
@Component({
  providers: [LocalService]  // 每次建立元件都會建立新的 LocalService
})
export class MyComponent {
  constructor(private localService: LocalService) { }
}

📌 HttpClient 發送 HTTP 請求

// data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';

interface Product {
  id: number;
  name: string;
  price: number;
}

@Injectable({ providedIn: 'root' })
export class DataService {
  private apiUrl = 'https://api.example.com/products';

  constructor(private http: HttpClient) { }

  // GET — 取得所有產品
  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(this.apiUrl);
  }

  // GET — 取得單一產品
  getProduct(id: number): Observable<Product> {
    return this.http.get<Product>(`${this.apiUrl}/${id}`);
  }

  // POST — 新增產品
  addProduct(product: Product): Observable<Product> {
    const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
    return this.http.post<Product>(this.apiUrl, product, { headers });
  }

  // PUT — 更新產品
  updateProduct(product: Product): Observable<Product> {
    return this.http.put<Product>(`${this.apiUrl}/${product.id}`, product);
  }

  // DELETE — 刪除產品
  deleteProduct(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}

📌 Observable 與 RxJS 基礎

Angular 的 HTTP 請求回傳的不是 Promise,而是 Observable

// Observable vs Promise 對比
// Promise(原生 JS / fetch):
fetch('/api/products')
  .then(res => res.json())
  .then(data => console.log(data));

// Observable(Angular / RxJS):
this.http.get<Product[]>('/api/products')
  .subscribe(data => console.log(data));

為什麼用 Observable?

import { Observable, interval, Subject } from 'rxjs';
import { map, filter, takeUntil } from 'rxjs/operators';

// 1. 可以取消(Promise 不行!)
const subscription = this.dataService.getProducts().subscribe(data => {
  this.products = data;
});
subscription.unsubscribe(); // 取消請求

// 2. 可以用操作符轉換資料流
this.dataService.getProducts().pipe(
  map(products => products.filter(p => p.price > 100)),  // 過濾貴的產品
  map(products => products.sort((a, b) => a.price - b.price))  // 按價格排序
).subscribe(filtered => {
  this.expensiveProducts = filtered;
});

// 3. 可以處理連續的事件(如 WebSocket、使用者輸入)
// 這是 Promise 做不到的
interval(1000).pipe(
  map(n => `第 ${n} 秒`),
  takeUntil(this.destroy$)  // 元件銷毀時自動停止
).subscribe(msg => console.log(msg));

📌 完整範例:呼叫 REST API

// product-list.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-product-list',
  template: `
    <h2>產品列表</h2>
    <div *ngIf="loading">載入中...</div>
    <div *ngIf="error" class="error">{{ error }}</div>
    <ul *ngIf="!loading && !error">
      <li *ngFor="let product of products">
        {{ product.name }} — NT$ {{ product.price }}
      </li>
    </ul>
    <button (click)="refresh()">重新載入</button>
  `
})
export class ProductListComponent implements OnInit, OnDestroy {
  products: Product[] = [];
  loading = false;
  error = '';
  private destroy$ = new Subject<void>();

  constructor(private dataService: DataService) { }

  ngOnInit() {
    this.loadProducts();
  }

  loadProducts() {
    this.loading = true;
    this.error = '';
    this.dataService.getProducts().pipe(
      takeUntil(this.destroy$)
    ).subscribe({
      next: (data) => {
        this.products = data;
        this.loading = false;
      },
      error: (err) => {
        this.error = '載入失敗:' + err.message;
        this.loading = false;
      }
    });
  }

  refresh() {
    this.loadProducts();
  }

  ngOnDestroy() {
    this.destroy$.next();     // 發出銷毀信號
    this.destroy$.complete(); // 清理所有訂閱
  }
}

📌 小結

  • 服務(Service)負責商業邏輯和資料存取,元件(Component)負責畫面
  • Angular 的 DI 和 ASP.NET Core 的 DI 概念相同:建構子注入、生命週期管理
  • @Injectable({ providedIn: 'root' }) 等同 ASP.NET Core 的 AddSingleton
  • HttpClient 回傳 Observable,比 Promise 更強大(可取消、可組合、可處理串流)
  • 務必在 ngOnDestroy 中取消訂閱,避免記憶體洩漏

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊