Angular學習筆記9:服務(Service)(2)
可觀察的物件(observable)的資料
在現在的是情況下:HeroService.getHeroes()
的函式簽名是同步的,它所隱含的假設是 HeroService
總是能同步獲取英雄列表資料。 而 HeroesComponent
也同樣假設能同步取到 getHeroes()
的結果。但是在實際的專案中,這種情況幾乎是不可能實現的,因為,在實際的專案中,這些資料是來源於遠端的伺服器上,而這個過程始終是一個非同步的過程。
在實際的專案中:HeroService
必須等伺服器給出響應, 而 getHeroes()
HeroService.getHeroes()
必須具有某種形式的非同步函式簽名。它可以使用回撥函式,可以返回 Promise
(承諾),也可以返回 Observable
(可觀察物件)。
一. 重新寫一個可觀察物件版本的 HeroService
Observable簡介:Observable
是 RxJS中的一個關鍵類。
重寫
1.開啟 HeroService
檔案,並從 RxJS 中匯入 Observable
和 of
符號。
import { Observable, of } from 'rxjs';
2. 修改getHeroes
方法:
-
getHeroes(): Observable<Hero[]> {
-
return of(HEROES);
-
}
注意:of(HEROES)
會返回一個 Observable<Hero[]>
,它會發出單個值,這個值就是這些模擬英雄的陣列。
3.在 HeroesComponent
中訂閱
之前的HeroService.getHeroes
Hero[]
, 現在它返回的是 Observable<Hero[]>
。所以現在必須在 HeroesComponent
中也向本服務中的這種Observable形式變化。
修改getHeroes():
-
getHeroes(): void {
-
this.heroService.getHeroes()
-
.subscribe(heroes => this.heroes = heroes);
-
}
Observable.subscribe()
是關鍵的差異點。
兩個版本的差別:
第一個版本:
hero的陣列賦值給了該元件的 heroes
屬性。 這種賦值是同步的,這裡包含的假設是伺服器能立即返回hero陣列或者瀏覽器能在等待伺服器響應時凍結介面,使其等到後端的資料返回以後,介面在再次解凍。
第二個版本:
應用會等待Observable
發出這個hero陣列,這種請求可能立即發生,也可能會在幾分鐘之後。 然後,subscribe
函式把這個hero陣列傳給這個回撥函式,該函式把hero陣列賦值給元件的 heroes
屬性。使用這種非同步方式,當 HeroService
從遠端伺服器獲取hero資料時,應用就可以工作了。(此過程可以想像類似的想像於將介面暫時凍結,然後等到遠端的伺服器將需要的資料返回以後,自動的將介面又重新的解凍,使應用重新開始工作)
二. 顯示資訊
學習目標:
-
新增一個
MessagesComponent
,它在螢幕的底部顯示應用中的訊息。 -
建立一個可注入的、全應用級別的
MessageService
,用於傳送要顯示的訊息。 -
把
MessageService
注入到HeroService
中。 -
當
HeroService
成功獲取了英雄資料時顯示一條訊息。
1.建立MessagesComponent(推薦使用CLI建立)
-
wjydeMacBook-Pro:demo wjy$ ng generate component messages
-
CREATE src/app/messages/messages.component.css (0 bytes)
-
CREATE src/app/messages/messages.component.html (27 bytes)
-
CREATE src/app/messages/messages.component.spec.ts (642 bytes)
-
CREATE src/app/messages/messages.component.ts (277 bytes)
-
UPDATE src/app/app.module.ts (638 bytes)
CLI 在 src/app/messages
中建立了元件檔案,並且把 MessagesComponent
宣告在了 AppModule
中。
2.修改 AppComponent
的模板來顯示所生成的 MessagesComponent
-
<h1>{{title}}</h1>
-
<app-heroes></app-heroes>
-
<app-messages></app-messages>
<app-messages> 是MessagesComponent的 selector: 'app-messages'
3.建立 MessageService
-
wjydeMacBook-Pro:demo wjy$ ng generate service messages
-
CREATE src/app/messages.service.spec.ts (386 bytes)
-
CREATE src/app/messages.service.ts (137 bytes)
4.在MessagesService中增加一個message的變數,並且在寫一個新增message的方法和清空message的方法。
-
import {Injectable} from '@angular/core';
-
@Injectable({
-
providedIn: 'root'
-
})
-
export class MessageService {
-
// messages 變數
-
messages: string[] = [];
-
// 增加變數的函式
-
add(message: string) {
-
this.messages.push(message);
-
}
-
// 清空變數的函式
-
clear() {
-
this.messages = [];
-
}
-
// 構造方法
-
constructor() {
-
}
-
}
5.把它注入到 HeroService
中
開啟 HeroService
,將匯入 MessageService
。
import { MessageService } from './message.service';
6.修改這個建構函式
新增一個私有的 messageService
屬性引數。 Angular 將會在建立 HeroService
時把 MessageService
的單例注入到這個屬性中。
constructor(private messageService: MessageService) { }
7.從 HeroService
中傳送一條訊息
修改 getHeroes
方法,在獲取到英雄陣列時傳送一條訊息。
-
getHeroes(): Observable<Hero[]> {
-
this.messageService.add('HeroService: fetched heroes');
-
return of(HEROES);
-
}
8.從 HeroService
中顯示訊息
開啟 MessagesComponent
,並且匯入 MessageService
。
import { MessageService } from '../message.service';
修改MessagesComponent建構函式,新增一個 public 的 messageService
屬性。 Angular 將會在建立 MessagesComponent
的例項時 把 MessageService
的例項注入到這個屬性中。
此處設messageService為public是因為要在messageService在模版檔案中用到。
注意:⚠️Angular只會繫結公共的屬性
9.繫結到 MessageService
修改messageComponent的模版檔案
-
<div *ngIf="messageService.messages.length">
-
<h2>Messages</h2>
-
<button class="clear" (click)="messageService.clear()">clear
-
</button>
-
<div *ngFor='let message of messageService.messages'> {{message}}</div>
-
</div>
-
*ngIf
只有在有訊息時才會顯示訊息區。 -
*ngFor
用來在一系列<div>
元素中展示訊息列表。 -
Angular 的事件繫結把按鈕的
click
事件繫結到了MessageService.clear()這個方法上了
。
10.儲存執行,重新整理瀏覽器,介面如下
11.使Message更好看點
為MessagesComponent元件新增私有樣式
-
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;
-
}