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 移出來。
在專案根目錄執行:
```bash
ng generate service hero
```
執行後,`src/app` 底下會多出兩個檔案:
- `hero.service.ts`
- `hero.service.spec.ts`

打開 `src/app/hero.service.ts`,可以看到 Angular CLI 產生的基礎 service:
```ts
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` 裡加入:
```ts
providers: [HeroService],
```
這段設定會讓 `HeroService` 變成 `AppModule` 底下可以被注入的服務。若使用 Angular CLI,也可以在產生 service 時直接指定 module:
```bash
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`,可以改成以下形式:
```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`:
```ts
import { Observable } from 'rxjs/Rx';
import { of } from 'rxjs/observable/of';
```
接著把 `getHeroes()` 的回傳型別改成 `Observable<Hero[]>`:
```ts
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()` 是這樣:
```ts
getHeroes(): void {
this.heroes = this.heroService.getHeroes();
}
```
改成 Observable 後,要寫成:
```ts
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. 建立 service | `src/app/hero.service.ts` | 是否有 `@Injectable()` 與 `HeroService` class |
| 2. 註冊 provider | `src/app/app.module.ts` | Angular 5 寫法是否有 `providers: [HeroService]` |
| 3. 注入 component | `heroes.component.ts` | constructor 是否宣告 `private heroService: HeroService` |
| 4. 取得資料 | `heroes.component.ts` | 同步資料直接指定,Observable 資料用 `subscribe()` |
| 5. 保持責任分離 | component 與 service | component 顯示資料,service 管理資料取得邏輯 |
這篇的資訊增益其實就在這個檢查順序:遇到 `No provider for HeroService`,先看 providers;遇到 `Property subscribe does not exist`,通常是 service 還在回傳陣列;遇到畫面沒資料,先看 `ngOnInit()` 有沒有呼叫 `getHeroes()`。
延伸閱讀
- 建立一個 Angular 5 的專案
- Angular 組件間溝通教學:Input、Output、ViewChild 與 Service
- Angular Service 依賴注入教學:providers、InjectionToken 與分層注入
- Angular NgModule 完整解析:模組結構、依賴注入與 forRoot 用法
- Angular HttpClient 教學:GET、POST、Header、Params 與錯誤處理
常見問題
Angular Service 是什麼?
Angular Service 是用來封裝可重用邏輯的 class,常見用途包含資料取得、API 呼叫、狀態共享與工具函式。把資料邏輯放在 service,可以讓 component 專心處理畫面與使用者互動。
`ng generate service hero` 會產生哪些檔案?
Angular 5 專案中,`ng generate service hero` 會在 `src/app` 產生 `hero.service.ts` 與 `hero.service.spec.ts`。前者放 service 程式碼,後者放測試檔。
Angular Service 一定要放進 providers 嗎?
Angular 5 的 NgModule 寫法通常需要把 service 放進 `providers`。新版 Angular 常用 `@Injectable({ providedIn: 'root' })` 讓 service 自動在 root injector 提供,是否需要手動 providers 要看專案版本與 service 設計。
為什麼 component constructor 可以拿到 HeroService?
Angular dependency injection 會根據 constructor 參數型別尋找對應 provider。只要 `HeroService` 已被註冊,`constructor(private heroService: HeroService)` 就能取得 Angular 建立的服務實體。
`Observable.subscribe()` 什麼時候會用到?
當 service 回傳 Observable 時,component 需要呼叫 `subscribe()` 取得資料。Angular HttpClient 回傳的也是 Observable,所以早點熟悉 `subscribe()`,之後改接 API 會比較自然。
`of(HEROES)` 是真的非同步 API 嗎?
`of(HEROES)` 不是 HTTP API,而是把本地陣列包成 Observable。教學時使用 `of()` 的好處,是 component 可以先練習 Observable 寫法,日後再把資料來源換成 `HttpClient.get<Hero[]>()`。
沒有呼叫 `subscribe()` 會怎樣?
如果 Observable 沒有被訂閱,component 不會拿到資料。對 Angular HttpClient 來說,沒有訂閱時 request 也不會真正送出。
參考資料
- Angular Docs:Creating and using services(存取日期:2026-08-28)
- Angular CLI Reference:generate service(存取日期:2026-08-28)
- Angular Docs:Making requests - HTTP Observables(存取日期:2026-08-28)
- Angular Tour of Heroes:toh-pt4.zip
最後更新
本文最後更新於 2026-08-28。2017-12-24 的 Angular 5 學習筆記保留建立 `HeroService`、註冊 providers、注入 `HeroesComponent` 與改用 `Observable.subscribe()` 的主要內容,並補上 GEO 結構、FAQ、站內延伸閱讀與新版 Angular 文件脈絡。
關於作者 {#author}
Claire Chang | 企業 AI 導入與流程轉型顧問。專注於 AI Agent 架構設計、ERP 系統整合與企業 AI 治理。
首次發布:2017-12-24
