模組化建築外牆,象徵 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 移出來。

在專案根目錄執行:

```bash

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:

```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 與 servicecomponent 顯示資料,service 管理資料取得邏輯

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

延伸閱讀

常見問題

QAngular Service 是什麼?

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

Q`ng generate service hero` 會產生哪些檔案?

Angular 5 專案中,`ng generate service hero` 會在 `src/app` 產生 `hero.service.ts` 與 `hero.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 建立的服務實體。

Q`Observable.subscribe()` 什麼時候會用到?

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

Q`of(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 文件脈絡。

關於作者 {#author}

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

首次發布:2017-12-24