Angular-第7課-服務的進階應用

Vincent Zheng
新手工程師的程式教室
9 min readNov 26, 2018

上一課對服務有了基本的認識。這一課會先介紹網路傳輸常見的Observable物件與subscribe方法。再建立新的服務,注入到多個地方,來開發一個「訊息」的功能。

課程關鍵字:#服務 #注入 #observable #subsribe #callback

一、Observable物件

目前HeroService取得hero資料的方式是透過讀取檔案,由於馬上就能得到資料,因此屬於「同步任務」。但若是透過網路傳輸來取得,則是屬於「非同步任務」,理由是需要連線到伺服器,使用者的應用程式必須等待伺服器回傳資料。

後面的單元,會將Angular應用程式改為使用網路傳輸來取得資料。而呼叫相關的HTTP方法後會得到型態為「Observable」的物件,裡面裝著從網路取得的資料。Observable字面翻譯是「可觀察物件」,但我覺得用「可訂閱物件」來解釋會比較好懂。況且下一節要介紹的方法就叫「訂閱」。

用生活情境來比喻,Observable有點像是Youtube上的那些影片。我們訂閱了創作者的影片,未來新作品出現時就可接收到通知,以便觀眾前去觀賞。Angular也類似如此,在撰寫程式時訂閱(subscribe)Observable物件,當取得資料時才進行後續的動作。

現在將Service改為回傳Observable物件。請開啟hero.servoce.ts,將原先的getHeroes方法調整成如下(請自行匯入rxjs模組):

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

「of」方法是將參數轉換為Observable物件,此處是原先從外部讀取的Hero陣列。而回傳值型態的Observable泛型類別,需要提供回傳資料的實際類別。因此在這裡提供「Hero[]」。

二、訂閱

上一節將Observable比喻成「可訂閱物件」,而這一節要改寫組件的程式檔,進行訂閱(subscribe)。請開啟heroes.component.ts,將getHeroes方法改寫如下:

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

可以發現將原先heroes賦值的操作寫在subscribe方法中了。
透過heroService呼叫getHeroes方法時,這個組件會開始等待Service回傳資料。因為要等待,所以需要進行「訂閱」,以便組件在資料到手後,能進行下一步動作,也就是heroes的賦值。

讓我們詳細看一下subscribe的寫法:

.subscribe(heroes => this.heroes = heroes)

前面的heroes參數是指HeroService回傳Observable物件中的資料,Angular會自動傳入,不必手動撰寫。接著透過「=>」符號,將這個heroes帶領至後方的程式流程,這裡是將其賦予給本類別的heroes資料成員。像這種讓Angular自動呼叫的形式,稱為「回呼(callback)」。

完成後,可發現應用程式正常執行。透過Observable物件與subscribe方法的運用,使程式碼更接近透過網路取得資料的做法。

三、在服務注入其他服務

這節要為應用程式加入「訊息」的功能,用以顯示取得hero資料的歷史紀錄。新的組件後面會再建立,現在請先建立服務,名稱為message。

ng generate service message

在MessageService的類別中加入一個字串陣列messages,以及addMessage、clear兩個方法,如下所示:

import { Injectable } from '@angular/core';@Injectable({
providedIn: 'root',
})
export class MessageService {
messages: string[] = [];add(message: string): void {
this.messages.push(message);
}
clear(): void {
this.messages = [];
}
}

messages是個字串陣列,用來存放訊息,也就是本次功能所需的歷史紀錄。add方法會接收字串參數,添加到messages中。而clear方法則是藉由重新賦予一個空陣列,來覆蓋掉messages的舊資料。

現在MessageService準備完成。接著我們仿照第6課的做法,將它「注入」到HeroService中。開啟hero.service.ts,在建構子的參數中注入這個服務。

constructor(private messageService: MessageService) { }

我們希望取得hero陣列資料時,能新增一筆歷史紀錄在訊息陣列中。因此請在getHeroes方法添加一行程式,呼叫MessageService的add方法來新增,內容為「HeroesService: fetched heroes」。

getHeroes(): Observable<Hero[]> {
this.messageService.add('HeroService: fetched heroes');
return of(HEROES);
}

完成後,打開程式確認有無異常。現在載入HeroesComponent時,已經在剛剛的MessageService的陣列中放入資料了,只是目前沒有組件來顯示它。

四、公開的服務

請建立新的組件,名稱為:messages。這一節我們要用它來展示前面的message資料。

ng generate component messages

將MessagesComponent組件放置在AppComponent中的底部。因此app.component.html會修改成如下:

<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>

接著再把上一節建立的MessageService注入到這個組件,請開啟messages.component.ts,在建構子的參數中注入服務。

constructor(public messageService: MessageService) {}

此處比較不一樣的是冠上了public,將它做為公開的服務,而非私有(private)的,這是因為等等會在外部的html檔中直接使用。

開啟message.component.html,將版面配置改為如下:

<div *ngIf="messageService.messages.length">  <h2>Messages</h2>  <button class="clear"
(click)="messageService.clear()">clear</button>
<div *ngFor='let msg of messageService.messages'> {{msg}} </div></div>

趁這個機會用上面的html檔,複習一下前面所學的語法。
「*ngIf」:條件判斷。當messages陣列有元素時才會顯示這個區塊。
「*ngFor」:迴圈。將messages陣列的元素,透過插值綁定列示出來。
「click」:點擊事件。當這個按鈕被點擊時,會執行service的clear方法。

在前面的messages.component.ts中,MessageService是以公開服務的方式來注入進來。所以在html檔才能夠直接存取messageService這個成員。
若不想這麼做,也可修改為私有(private)服務,只是要在ts檔另外宣告方法,以提供html檔需要的資料。

最後請套用官方提供的CSS樣式,貼到messages.component.css中。

/* MessagesComponent's private CSS styles */
h2 {
color: red;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[text], button {
color: crimson;
font-family: Cambria, Georgia;
}
button.clear {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #aaa;
cursor: auto;
}
button.clear {
color: #888;
margin-bottom: 12px;
}

開啟應用程式,在畫面底部看見了「訊息」的區塊。並帶有一則訊息,這是隨著heroes組件載入資料時所產生的。點擊Clear按鈕,這個區塊便因為messages陣列沒有資料而消失了。

本課開發的訊息區塊

五、總結

在上一課與本課,我們認識了「服務」。組件可以專注於展示資料,不需將取得資料的實作方式撰寫在組件的程式檔中。若沒有服務來抽離程式碼,程式雖能照常運行,但組件的程式檔會隨著專案規模擴大而越來越雜亂。

將取得資料的過程交由服務來處理,能讓不同的程式檔(組件ts檔與服務ts檔)各司其職,變得精簡。此外,服務還能被注入到各種需要的地方,進而達到程式碼重複利用的效益。

--

--

Vincent Zheng
新手工程師的程式教室

我是Vincent,是個來自資管系的後端軟體工程師。當初因為學校作業,才踏出寫部落格的第一步。這裡提供程式教學文章,包含自學和工作上用到的經驗,希望能讓讀者學到東西。我的部落已搬家至 https://chikuwa-tech-study.blogspot.com/