💉 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中取消訂閱,避免記憶體洩漏