模組化建築外牆,象徵 Angular Service 透過依賴注入提供應用程式資料

← INSIGHTS & PERSPECTIVES | 前端開發

Angular Service 建立教學:用 CLI 產生 HeroService 並回傳 Observable

用 Angular CLI 建立 HeroService,註冊 providers,注入 HeroesComponent,並用 Observable 與 subscribe 取得英雄資料。

Angular Service 適合把資料取得、商業邏輯或跨元件共用功能從 component 裡拆出來。這篇接續 Angular 主從元件範例,示範如何用 Angular CLI 建立 HeroService,把英雄資料移到 service,再讓 HeroesComponent 透過 dependency injection 取得資料;最後把同步回傳改成 Observable,讓程式更接近日後串接 HTTP API 的寫法。

上一篇 Angular 主從元件開發的範例檔案可參考 Angular 官方 Tour of Heroes: toh-pt3.zip

如何用 Angular CLI 建立 HeroService?

Angular CLI 可以用 ng generate service hero 建立一個名為 HeroService 的服務。Angular 5 專案會產生 service class 與 spec 測試檔,讓資料邏輯可以從 component 移出來。

在專案根目錄執行:

ng generate service hero

執行後,src/app 底下會多出兩個檔案:

  • hero.service.ts
  • hero.service.spec.ts
Angular CLI 建立 HeroService 後產生 hero.service.ts 與 hero.service.spec.ts

打開 src/app/hero.service.ts,可以看到 Angular CLI 產生的基礎 service:

import { Injectable } from '@angular/core';

@Injectable()
export class HeroService {
  constructor() {}
}

@Injectable() 代表 HeroService 可以參與 Angular dependency injection。對 service 來說,即使目前 constructor 裡還沒有其他依賴,也建議保留這個 decorator,之後要注入 HTTP client、logger 或其他 service 時才不需要再回頭補。

新版 Angular 常見寫法會使用 @Injectable({ providedIn: 'root' })inject()。這篇保留 Angular 5 時期的 NgModule 寫法,因為來源範例的重點是理解 service、providers 與 component 注入之間的關係。

HeroService 要如何提供給 Angular 應用程式使用?

Angular 5 專案需要把 HeroService 放進 providers,Angular injector 才知道如何建立這個服務。最常見的位置是 AppModule,這樣整個應用程式都能注入同一個服務實體。

打開 src/app/app.module.ts,在 @NgModule 裡加入:

providers: [HeroService],

這段設定會讓 HeroService 變成 AppModule 底下可以被注入的服務。若使用 Angular CLI,也可以在產生 service 時直接指定 module:

ng generate service hero --module=app

--module=app 會請 Angular CLI 協助更新 app.module.ts 的 providers 設定。Angular 官方文件也把 service 定位為可重用邏輯,例如資料存取、API 呼叫或共用功能;而 provider 的設定位置會影響 service 實體的生命週期與共享範圍(Angular Docs,2026 年存取)。

如何讓 HeroesComponent 使用 HeroService 的資料?

HeroesComponent 使用 HeroService 時,要先 import service,再透過 constructor 注入。component 不再直接讀取 HEROES 常數,而是呼叫 heroService.getHeroes() 取得資料。

打開 src/app/heroes/heroes.component.ts,可以改成以下形式:

import { Component, OnInit } from '@angular/core';
import { HeroService } from '../hero.service';
import { Hero } from '../hero';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css'],
})
export class HeroesComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private heroService: HeroService) {}

  getHeroes(): void {
    this.heroes = this.heroService.getHeroes();
  }

  ngOnInit() {
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

這段程式有幾個重點:

  • import { HeroService } from '../hero.service';:引入剛建立的 service。
  • heroes: Hero[];:先宣告英雄列表,不在屬性宣告時直接填值。
  • constructor(private heroService: HeroService) {}:請 Angular 注入 HeroService
  • this.heroService.getHeroes():從 service 取得資料。
  • ngOnInit():元件初始化時呼叫 getHeroes()

這樣做的好處,是 HeroesComponent 開始只負責顯示與互動,資料來源則交給 HeroService。等資料改成從 HTTP API 取得時,component 的責任仍然可以維持乾淨。

為什麼 getHeroes 要改成 Observable?

Observable 是 RxJS 提供的非同步資料流型別。Angular Service 若未來會串接 HTTP API,讓 getHeroes() 回傳 Observable<Hero[]>,component 就可以用同一種方式處理本地假資料與遠端資料。

先在 src/app/hero.service.ts 匯入 Observable 與 of

import { Observable } from 'rxjs/Rx';
import { of } from 'rxjs/observable/of';

接著把 getHeroes() 的回傳型別改成 Observable<Hero[]>

getHeroes(): Observable<Hero[]> {
  return of(HEROES);
}

of(HEROES) 會把既有的 HEROES 陣列包成一個 Observable。這在教學階段很方便,因為資料仍然來自本地常數,但 component 已經開始使用非同步資料流的寫法。

如果之後改用 HttpClient.get<Hero[]>(),Angular HttpClient 同樣會回傳 Observable<Hero[]>。Angular 官方文件也提醒,HttpClient 的 Observable 是 cold Observable,只有被訂閱時才會真正送出 request(Angular Docs,2026 年存取)。

HeroesComponent 要如何訂閱 Observable?

HeroesComponent 接收 Observable<Hero[]> 時,不能再直接把回傳值指定給 this.heroes。component 必須呼叫 subscribe(),等 Observable 發出資料後再更新畫面狀態。

原本同步版本的 getHeroes() 是這樣:

getHeroes(): void {
  this.heroes = this.heroService.getHeroes();
}

改成 Observable 後,要寫成:

getHeroes(): void {
  this.heroService
    .getHeroes()
    .subscribe(heroes => (this.heroes = heroes));
}

Observable.subscribe() 是這裡最關鍵的差異。同步資料可以直接指定;Observable 則代表資料可能稍後才回來,所以要在 subscription callback 裡更新 heroes

實務上,這種寫法也比較接近真實 API 呼叫。使用者操作畫面時,瀏覽器不需要因為等待伺服器回應而凍結 UI;資料回來後,component 再把最新結果寫回畫面。

建立 Angular Service 的實作順序怎麼記?

Angular Service 新手最容易卡住的地方,是建立 service、註冊 provider、注入 component、訂閱 Observable 這四件事缺一不可。照順序檢查,通常很快能找到錯誤位置。

我自己會用這個順序檢查:

步驟檔案要檢查的事
1. 建立 servicesrc/app/hero.service.ts是否有 @Injectable()HeroService class
2. 註冊 providersrc/app/app.module.tsAngular 5 寫法是否有 providers: [HeroService]
3. 注入 componentheroes.component.tsconstructor 是否宣告 private heroService: HeroService
4. 取得資料heroes.component.ts同步資料直接指定,Observable 資料用 subscribe()
5. 保持責任分離component 與 servicecomponent 顯示資料,service 管理資料取得邏輯

這篇的資訊增益其實就在這個檢查順序:遇到 No provider for HeroService,先看 providers;遇到 Property subscribe does not exist,通常是 service 還在回傳陣列;遇到畫面沒資料,先看 ngOnInit() 有沒有呼叫 getHeroes()

常見問題

QAngular Service 是什麼?

Angular Service 是用來封裝可重用邏輯的 class,常見用途包含資料取得、API 呼叫、狀態共享與工具函式。把資料邏輯放在 service,可以讓 component 專心處理畫面與使用者互動。

Qng generate service hero 會產生哪些檔案?

Angular 5 專案中,ng generate service hero 會在 src/app 產生 hero.service.tshero.service.spec.ts。前者放 service 程式碼,後者放測試檔。

QAngular Service 一定要放進 providers 嗎?

Angular 5 的 NgModule 寫法通常需要把 service 放進 providers。新版 Angular 常用 @Injectable({ providedIn: 'root' }) 讓 service 自動在 root injector 提供,是否需要手動 providers 要看專案版本與 service 設計。

Q為什麼 component constructor 可以拿到 HeroService?

Angular dependency injection 會根據 constructor 參數型別尋找對應 provider。只要 HeroService 已被註冊,constructor(private heroService: HeroService) 就能取得 Angular 建立的服務實體。

QObservable.subscribe() 什麼時候會用到?

當 service 回傳 Observable 時,component 需要呼叫 subscribe() 取得資料。Angular HttpClient 回傳的也是 Observable,所以早點熟悉 subscribe(),之後改接 API 會比較自然。

Qof(HEROES) 是真的非同步 API 嗎?

of(HEROES) 不是 HTTP API,而是把本地陣列包成 Observable。教學時使用 of() 的好處,是 component 可以先練習 Observable 寫法,日後再把資料來源換成 HttpClient.get<Hero[]>()

Q沒有呼叫 subscribe() 會怎樣?

如果 Observable 沒有被訂閱,component 不會拿到資料。對 Angular HttpClient 來說,沒有訂閱時 request 也不會真正送出。

參考資料

延伸閱讀

最後更新

本文最後更新於 2026-08-28。2017-12-24 的 Angular 5 學習筆記保留建立 HeroService、註冊 providers、注入 HeroesComponent 與改用 Observable.subscribe() 的主要內容,並補上 GEO 結構、FAQ、站內延伸閱讀與新版 Angular 文件脈絡。

關於作者

Claire Chang | 企業 AI 導入與流程轉型顧問。專注於 AI Agent 架構設計、ERP 系統整合與企業 AI 治理。

首次發布:2017-12-24