前言

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 應用程式結構更清晰、更容易維護。

參考資料