英雄指南的 HeroesComponent
目前獲取和顯示的都是模擬數據。css
本節課的重構完成以後,HeroesComponent
變得更精簡,而且聚焦於爲它的視圖提供支持。這也讓它更容易使用模擬服務進行單元測試。html
若是你但願從 GitHub 上查看咱們提供測試的源代碼,你能夠訪問下面的連接:https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-servicesreact
組件不該該直接獲取或保存數據,它們不該該瞭解是否在展現假數據。 它們應該聚焦於展現數據,而把數據訪問的職責委託給某個服務。git
本節課,你將建立一個 HeroService
,應用中的全部類均可以使用它來獲取英雄列表。 不要使用 new
來建立此服務,而要依靠 Angular 的依賴注入機制把它注入到 HeroesComponent
的構造函數中。github
服務是在多個「互相不知道」的類之間共享信息的好辦法。 你將建立一個 MessageService
,而且把它注入到兩個地方:api
HeroService
中,它會使用該服務發送消息。MessagesComponent
中,它會顯示其中的消息。HeroService
使用 Angular CLI 建立一個名叫 hero
的服務。數組
|
該命令會在src/app/hero.service.ts
中生成HeroService
類的骨架。HeroService
類的代碼以下:緩存
src/app/hero.service.ts (new service)服務器
|
注意,這個新的服務導入了 Angular 的 Injectable 符號,而且給這個服務類添加了 @
Injectable()
裝飾器。 它把這個類標記爲依賴注入系統的參與者之一。HeroService
類將會提供一個可注入的服務,而且它還能夠擁有本身的待注入的依賴。 目前它尚未依賴,可是很快就會有了。
@
Injectable()
裝飾器會接受該服務的元數據對象,就像 @
Component()
對組件類的做用同樣。
HeroService
能夠從任何地方獲取數據:Web 服務、本地存儲(LocalStorage)或一個模擬的數據源。
從組件中移除數據訪問邏輯,意味着未來任什麼時候候你均可以改變目前的實現方式,而不用改動任何組件。 這些組件不須要了解該服務的內部實現。
這節課中的實現仍然會提供模擬的英雄列表。
導入 Hero
和 HEROES
。
|
添加一個 getHeroes
方法,讓它返回模擬的英雄列表。
|
HeroService
在要求 Angular 把 HeroService
注入到 HeroesComponent
以前,你必須先把這個服務提供給依賴注入系統。稍後你就要這麼作。 你能夠經過註冊提供商來作到這一點。提供商用來建立和交付服務,在這個例子中,它會對 HeroService
類進行實例化,以提供該服務。
如今,你須要確保 HeroService
已經做爲該服務的提供商進行過註冊。 你要用一個注入器註冊它。注入器就是一個對象,負責在須要時選取和注入該提供商。
默認狀況下,Angular CLI 命令 ng generate service
會經過給 @
Injectable 裝飾器添加元數據的形式,用根注入器將你的服務註冊成爲提供商。
若是你看看 HeroService
緊前面的 @
Injectable()
語句定義,就會發現 providedIn 元數據的值是 'root':
|
@
({ providedIn: 'root', })
當你在頂層提供該服務時,Angular 就會爲 HeroService
建立一個單一的、共享的實例,並把它注入到任何想要它的類上。 在 @
Injectable 元數據中註冊該提供商,還能容許 Angular 經過移除那些徹底沒有用過的服務來進行優化。
要了解關於提供商的更多知識,參見提供商部分。 要了解關於注入器的更多知識,參見依賴注入指南。
如今 HeroService
已經準備好插入到 HeroesComponent
中了。
這是一個過渡性的代碼範例,它將會容許你提供並使用 HeroService
。此刻的代碼和最終代碼相差很大。
HeroesComponent
打開 HeroesComponent
類文件。
刪除 HEROES
的導入語句,由於你之後不會再用它了。 轉而導入 HeroService
。
src/app/heroes/heroes.component.ts (import HeroService)
|
把 heroes
屬性的定義改成一句簡單的聲明。
|
HeroService
往構造函數中添加一個私有的 heroService
,其類型爲 HeroService
。
|
這個參數同時作了兩件事:1. 聲明瞭一個私有 heroService
屬性,2. 把它標記爲一個 HeroService
的注入點。
當 Angular 建立 HeroesComponent
時,依賴注入系統就會把這個 heroService
參數設置爲 HeroService
的單例對象。
建立一個函數,以從服務中獲取這些英雄數據。
|
ngOnInit
中調用它你當然能夠在構造函數中調用 getHeroes()
,但那不是最佳實踐。
讓構造函數保持簡單,只作初始化操做,好比把構造函數的參數賦值給屬性。 構造函數不該該作任何事。 它固然不該該調用某個函數來向遠端服務(好比真實的數據服務)發起 HTTP 請求。
而是選擇在 ngOnInit 生命週期鉤子中調用 getHeroes(),以後交由 Angular 處理,它會在構造出 HeroesComponent 的實例以後的某個合適的時機調用 ngOnInit。
|
刷新瀏覽器,該應用仍運行的一如既往。 顯示英雄列表,而且當你點擊某個英雄的名字時顯示出英雄詳情視圖。
HeroService.getHeroes()
的函數簽名是同步的,它所隱含的假設是 HeroService
老是能同步獲取英雄列表數據。 而 HeroesComponent
也一樣假設能同步取到 getHeroes()
的結果。
|
這在真實的應用中幾乎是不可能的。 如今能這麼作,只是由於目前該服務返回的是模擬數據。 不過很快,該應用就要從遠端服務器獲取英雄數據了,而那天生就是異步操做。
HeroService
必須等服務器給出響應, 而 getHeroes()
不能當即返回英雄數據, 瀏覽器也不會在該服務等待期間中止響應。
HeroService.getHeroes()
必須具備某種形式的異步函數簽名。
它可使用回調函數,能夠返回 Promise
(承諾),也能夠返回 Observable
(可觀察對象)。
這節課,HeroService.getHeroes()
將會返回 Observable
,由於它最終會使用 Angular 的 HttpClient.get
方法來獲取英雄數據,而 HttpClient.get()
會返回 Observable
。
HeroService
Observable
是 RxJS 庫中的一個關鍵類。
在稍後的 HTTP 教程中,你就會知道 Angular HttpClient 的方法會返回 RxJS 的 Observable
。 這節課,你將使用 RxJS 的 of()
函數來模擬從服務器返回數據。
打開 HeroService
文件,並從 RxJS 中導入 Observable
和 of
符號。
src/app/hero.service.ts (Observable imports)
|
把 getHeroes
方法改爲這樣:
|
of(HEROES)
會返回一個Observable<Hero[]>
,它會發出單個值,這個值就是這些模擬英雄的數組。
在 HTTP 教程中,你將會調用 HttpClient.get<Hero[]>()
它也一樣返回一個 Observable<Hero[]>
,它也會發出單個值,這個值就是來自 HTTP 響應體中的英雄數組。
HeroesComponent
中訂閱HeroService.getHeroes
方法以前返回一個 Hero[]
, 如今它返回的是 Observable<Hero[]>
。
你必須在 HeroesComponent
中也向本服務中的這種形式看齊。
找到 getHeroes
方法,而且把它替換爲以下代碼(和前一個版本對比顯示):
heroes.component.ts (Observable)
|
heroes.component.ts (Original)
|
Observable.subscribe()
是關鍵的差別點。
上一個版本把英雄的數組賦值給了該組件的 heroes
屬性。 這種賦值是同步的,這裏包含的假設是服務器能當即返回英雄數組或者瀏覽器能在等待服務器響應時凍結界面。
當 HeroService
真的向遠端服務器發起請求時,這種方式就行不通了。
新的版本等待 Observable
發出這個英雄數組,這可能當即發生,也可能會在幾分鐘以後。 而後,subscribe
函數把這個英雄數組傳給這個回調函數,該函數把英雄數組賦值給組件的 heroes
屬性。
使用這種異步方式,當 HeroService
從遠端服務器獲取英雄數據時,就能夠工做了。
在這一節,你將
MessagesComponent
,它在屏幕的底部顯示應用中的消息。MessageService
,用於發送要顯示的消息。MessageService
注入到 HeroService
中。HeroService
成功獲取了英雄數據時顯示一條消息。MessagesComponent
使用 CLI 建立 MessagesComponent
。
|
CLI 在 src/app/
messages 中建立了組件文件,而且把 MessagesComponent
聲明在了 AppModule
中。
修改 AppComponent
的模板來顯示所生成的 MessagesComponent
:
/src/app/app.component.html
|
你能夠在頁面的底部看到來自的 MessagesComponent
的默認內容。
MessageService
使用 CLI 在 src/app
中建立 MessageService
。
|
打開MessageService
,並把它的內容改爲這樣:
/src/app/message.service.ts
|
該服務對外暴露了它的 messages 緩存,以及兩個方法:add()
方法往緩存中添加一條消息,clear()
方法用於清空緩存。
HeroService
中從新打開 HeroService
,而且導入 MessageService
。
/src/app/hero.service.ts (import MessageService)
|
修改這個構造函數,添加一個私有的 messageService
屬性參數。 Angular 將會在建立 HeroService
時把 MessageService
的單例注入到這個屬性中。
|
這是一個典型的「服務中的服務」場景: 你把 MessageService
注入到了 HeroService
中,而 HeroService
又被注入到了 HeroesComponent
中。
HeroService
中發送一條消息修改 getHeroes
方法,在獲取到英雄數組時發送一條消息。
|
HeroService
中顯示消息MessagesComponent
能夠顯示全部消息, 包括當 HeroService
獲取到英雄數據時發送的那條。
打開 MessagesComponent
,而且導入 MessageService
。
/src/app/messages/messages.component.ts (import MessageService)
|
修改構造函數,添加一個 public 的 messageService
屬性。 Angular 將會在建立 MessagesComponent
的實例時 把 MessageService
的實例注入到這個屬性中。
|
這個messageService
屬性必須是公共屬性,由於你將會在模板中綁定到它。
Angular 只會綁定到組件的公共屬性。
MessageService
把 CLI 生成的 MessagesComponent
的模板改爲這樣:
src/app/messages/messages.component.html
|
這個模板直接綁定到了組件的 messageService
屬性上。
*
ngIf 只有在有消息時纔會顯示消息區。*
ngFor 用來在一系列 <div>
元素中展現消息列表。click
事件綁定到了 MessageService.clear()
。當你把 最終代碼 某一頁的內容添加到 messages.component.css
中時,這些消息會變得好看一些。
刷新瀏覽器,頁面顯示出了英雄列表。 滾動到底部,就會在消息區看到來自 HeroService
的消息。 點擊「清空」按鈕,消息區不見了。
你的應用應該變成了這樣 在線例子 / 下載範例。本頁所說起的代碼文件以下。
若是你想直接在 stackblitz 運行本頁中的例子,請單擊連接:https://stackblitz.com/github/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services
本頁中所說起的代碼以下:https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services
對應的文件列表和代碼連接以下:
HeroService
類中。HeroService
註冊爲該服務的提供商,以便在別處能夠注入它。HeroService
中獲取數據的方法提供了一個異步的函數簽名。Observable
以及 RxJS 庫。of()
方法返回了一個模擬英雄數據的可觀察對象 (Observable<Hero[]>
)。ngOnInit
生命週期鉤子中調用 HeroService
方法,而不是構造函數中。MessageService
,以便在類之間實現鬆耦合通信。HeroService
連同注入到它的服務 MessageService
一塊兒,注入到了組件中。