前言
Service 是 Angular 中用來封裝業務邏輯和數據操作的核心概念。透過 Dependency Injection(依賴注入),Service 可以在不同的 Component 之間共享資料和功能。這篇文章將介紹 Service 的用途和使用方式。
Service 用途
Service 是可以 injectable 的一種 class,可以將 Angular Web App 的業務邏輯和數據操作從 Component 中分離出來。
主要功能:
- 封裝可重複使用的業務邏輯
- 管理應用程式的狀態和資料
- 處理 HTTP 請求
- Component 之間的資料共享
- 與後端 API 溝通
讓 Component 可以更容易進行數據間的溝通,而不需要直接進行數據操作。也可以將 Service 注入到其他 Service,也就是所謂的 injectable。
建立 Service
使用 Angular CLI 建立 Service:
ng generate service services/user
這會建立兩個檔案:
user.service.ts:Service 的主要檔案user.service.spec.ts:測試檔案
基本的 Service 範例:
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root", // 在 root 層級提供,整個應用程式都可使用
})
export class UserService {
private users: string[] = ["John", "Mary", "David"];
constructor() {}
getUsers(): string[] {
return this.users;
}
addUser(name: string): void {
this.users.push(name);
}
deleteUser(name: string): void {
const index = this.users.indexOf(name);
if (index > -1) {
this.users.splice(index, 1);
}
}
}
Service 的注入
Service 可以通過注入方式在 Component 之間進行共享,使用 Dependency Injection 來實現注入。
當一個 Component 需要使用某個 Service 的時候,可以在該 Component 的 constructor 中聲明一個對應的參數。當該 Component 被創建時,就可以在 Component 中呼叫該 Service 中的物件或方法。
import { Component, OnInit } from "@angular/core";
import { UserService } from "./services/user.service";
@Component({
selector: "app-user-list",
template: `
<h1>User List</h1>
<ul>
<li *ngFor="let user of users">{{ user }}</li>
</ul>
`,
})
export class UserListComponent implements OnInit {
users: string[] = [];
// 在這注入要使用的 Service
constructor(private userService: UserService) {}
ngOnInit(): void {
this.users = this.userService.getUsers();
}
}
在上面的範例中,constructor 聲明了 userService,從而實現了對 UserService 的注入。通過調用 userService.getUsers() 取得用戶列表。
HTTP Service 範例
實際開發中,Service 最常用於處理 HTTP 請求:
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";
export interface User {
id: number;
name: string;
email: string;
}
@Injectable({
providedIn: "root",
})
export class UserService {
private apiUrl = "https://api.example.com/users";
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUserById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
createUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
updateUser(id: number, user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${id}`, user);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}
在 Component 中使用:
export class UserComponent implements OnInit {
users: User[] = [];
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userService.getUsers().subscribe(
(users) => (this.users = users),
(error) => console.error("Error loading users:", error)
);
}
addUser(user: User): void {
this.userService.createUser(user).subscribe(
(newUser) => {
this.users.push(newUser);
console.log("User created successfully");
},
(error) => console.error("Error creating user:", error)
);
}
}
Provider 設定
@Injectable 裝飾器的 providedIn 屬性決定 Service 的作用範圍:
providedIn: ‘root’
最常用的設定,Service 在整個應用程式中是 單例(Singleton)。
@Injectable({
providedIn: "root",
})
export class UserService {}
優點:
- 整個應用程式共享同一個實例
- 自動 Tree-shakable(未使用的 Service 會被移除)
- 不需要在 module 的 providers 中註冊
providedIn: ‘any’
每個 lazy-loaded module 都會有自己的實例。
@Injectable({
providedIn: "any",
})
export class LogService {}
Module 層級 Provider
在特定 Module 中提供:
@NgModule({
providers: [UserService],
})
export class UserModule {}
Component 層級 Provider
在特定 Component 中提供,每個 Component 實例都會有獨立的 Service 實例:
@Component({
selector: "app-user",
templateUrl: "./user.component.html",
providers: [UserService], // 每個 Component 實例都有自己的 Service
})
export class UserComponent {}
Service 之間的依賴
Service 可以注入其他 Service:
@Injectable({
providedIn: "root",
})
export class LogService {
log(message: string): void {
console.log(`[LOG] ${new Date().toISOString()}: ${message}`);
}
}
@Injectable({
providedIn: "root",
})
export class UserService {
constructor(
private http: HttpClient,
private logService: LogService // 注入另一個 Service
) {}
getUsers(): Observable<User[]> {
this.logService.log("Fetching users...");
return this.http.get<User[]>(this.apiUrl);
}
}
BehaviorSubject 共享資料
使用 BehaviorSubject 在多個 Component 之間共享狀態:
import { Injectable } from "@angular/core";
import { BehaviorSubject, Observable } from "rxjs";
@Injectable({
providedIn: "root",
})
export class DataService {
private dataSource = new BehaviorSubject<string>("Initial data");
currentData: Observable<string> = this.dataSource.asObservable();
constructor() {}
changeData(data: string): void {
this.dataSource.next(data);
}
}
在 Component 中使用:
// Component A
export class ComponentA {
constructor(private dataService: DataService) {}
updateData(): void {
this.dataService.changeData("New data from Component A");
}
}
// Component B
export class ComponentB implements OnInit {
data: string;
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.dataService.currentData.subscribe((data) => (this.data = data));
}
}
實用範例:認證 Service
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { BehaviorSubject, Observable } from "rxjs";
import { tap } from "rxjs/operators";
interface LoginResponse {
token: string;
user: User;
}
@Injectable({
providedIn: "root",
})
export class AuthService {
private currentUserSubject = new BehaviorSubject<User | null>(null);
public currentUser = this.currentUserSubject.asObservable();
constructor(private http: HttpClient) {
// 從 localStorage 載入已登入的使用者
const user = localStorage.getItem("currentUser");
if (user) {
this.currentUserSubject.next(JSON.parse(user));
}
}
login(username: string, password: string): Observable<LoginResponse> {
return this.http
.post<LoginResponse>("/api/auth/login", { username, password })
.pipe(
tap((response) => {
// 儲存使用者資訊和 token
localStorage.setItem("currentUser", JSON.stringify(response.user));
localStorage.setItem("token", response.token);
this.currentUserSubject.next(response.user);
})
);
}
logout(): void {
localStorage.removeItem("currentUser");
localStorage.removeItem("token");
this.currentUserSubject.next(null);
}
isLoggedIn(): boolean {
return !!this.currentUserSubject.value;
}
getToken(): string | null {
return localStorage.getItem("token");
}
}
總結
Angular Service 是應用程式架構的核心:
- 封裝業務邏輯:將複雜的邏輯從 Component 中分離
- 依賴注入:透過 DI 系統輕鬆共享功能
- 資料共享:使用 BehaviorSubject 在 Component 之間共享狀態
- HTTP 操作:處理與後端的資料交換
- 可重複使用:同一個 Service 可以被多個 Component 使用
- 易於測試:Service 可以獨立測試,也可以在測試時輕鬆 mock
掌握 Service 的使用,可以讓你的 Angular 應用程式結構更清晰、更容易維護。