Angular 服務(wù)

英雄指南的HeroesComponent目前獲取和顯示的都是模擬數(shù)據(jù)。

本節(jié)課的重構(gòu)完成之后,HeroesComponent變得更精簡(jiǎn),并且聚焦于為它的視圖提供支持。這也讓它更容易使用模擬服務(wù)進(jìn)行單元測(cè)試。

如果你希望從 GitHub 上查看我們提供測(cè)試的源代碼,你可以訪問下面的鏈接:https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services

為什么需要服務(wù)

組件不應(yīng)該直接獲取或保存數(shù)據(jù),它們不應(yīng)該了解是否在展示假數(shù)據(jù)。 它們應(yīng)該聚焦于展示數(shù)據(jù),而把數(shù)據(jù)訪問的職責(zé)委托給某個(gè)服務(wù)。

本節(jié)課,你將創(chuàng)建一個(gè)HeroService,應(yīng)用中的所有類都可以使用它來獲取英雄列表。 不要使用new來創(chuàng)建此服務(wù),而要依靠 Angular 的依賴注入機(jī)制把它注入到HeroesComponent的構(gòu)造函數(shù)中。

服務(wù)是在多個(gè)“互相不知道”的類之間共享信息的好辦法。 你將創(chuàng)建一個(gè)MessageService,并且把它注入到兩個(gè)地方:

HeroService中,它會(huì)使用該服務(wù)發(fā)送消息。

MessagesComponent中,它會(huì)顯示其中的消息。

創(chuàng)建HeroService

使用 Angular CLI 創(chuàng)建一個(gè)名叫hero的服務(wù)。

ng generate service hero

該命令會(huì)在src/app/hero.service.ts中生成HeroService類的骨架。HeroService類的代碼如下:

src/app/hero.service.ts (new service)

import{ Injectable } from?'@angular/core';


@Injectable({

??providedIn:?'root',

})

export?classHeroService {


??constructor() { }


}

@Injectable()服務(wù)

注意,這個(gè)新的服務(wù)導(dǎo)入了 Angular 的Injectable符號(hào),并且給這個(gè)服務(wù)類添加了@Injectable()裝飾器。 它把這個(gè)類標(biāo)記為依賴注入系統(tǒng)的參與者之一。HeroService類將會(huì)提供一個(gè)可注入的服務(wù),并且它還可以擁有自己的待注入的依賴。 目前它還沒有依賴,但是很快就會(huì)有了。

@Injectable()裝飾器會(huì)接受該服務(wù)的元數(shù)據(jù)對(duì)象,就像@Component()對(duì)組件類的作用一樣。

獲取英雄數(shù)據(jù)

HeroService可以從任何地方獲取數(shù)據(jù):Web 服務(wù)、本地存儲(chǔ)(LocalStorage)或一個(gè)模擬的數(shù)據(jù)源。

從組件中移除數(shù)據(jù)訪問邏輯,意味著將來任何時(shí)候你都可以改變目前的實(shí)現(xiàn)方式,而不用改動(dòng)任何組件。 這些組件不需要了解該服務(wù)的內(nèi)部實(shí)現(xiàn)。

這節(jié)課中的實(shí)現(xiàn)仍然會(huì)提供模擬的英雄列表。

導(dǎo)入Hero和HEROES。

import{ Hero } from?'./hero';

import{ HEROES } from?'./mock-heroes';

添加一個(gè)getHeroes方法,讓它返回模擬的英雄列表

getHeroes(): Hero[] {

??returnHEROES;

}

提供(provide)HeroService

在要求 Angular 把HeroService注入到HeroesComponent之前,你必須先把這個(gè)服務(wù)提供給依賴注入系統(tǒng)稍后你就要這么做。 你可以通過注冊(cè)提供商來做到這一點(diǎn)。提供商用來創(chuàng)建和交付服務(wù),在這個(gè)例子中,它會(huì)對(duì)HeroService類進(jìn)行實(shí)例化,以提供該服務(wù)。

現(xiàn)在,你需要確保HeroService已經(jīng)作為該服務(wù)的提供商進(jìn)行過注冊(cè)。 你要用一個(gè)注入器注冊(cè)它。注入器就是一個(gè)對(duì)象,負(fù)責(zé)在需要時(shí)選取和注入該提供商。

默認(rèn)情況下,Angular CLI 命令ng generate service會(huì)通過給@Injectable裝飾器添加元數(shù)據(jù)的形式,用根注入器將你的服務(wù)注冊(cè)成為提供商。

如果你看看HeroService緊前面的@Injectable()語句定義,就會(huì)發(fā)現(xiàn)providedIn元數(shù)據(jù)的值是 'root':

@Injectable({

??providedIn:?'root',

})

@

Injectable

({? providedIn:'root',})

當(dāng)你在頂層提供該服務(wù)時(shí),Angular 就會(huì)為HeroService創(chuàng)建一個(gè)單一的、共享的實(shí)例,并把它注入到任何想要它的類上。 在@Injectable元數(shù)據(jù)中注冊(cè)該提供商,還能允許 Angular 通過移除那些完全沒有用過的服務(wù)來進(jìn)行優(yōu)化。

要了解關(guān)于提供商的更多知識(shí),參見提供商部分。 要了解關(guān)于注入器的更多知識(shí),參見依賴注入指南。

現(xiàn)在HeroService已經(jīng)準(zhǔn)備好插入到HeroesComponent中了。

這是一個(gè)過渡性的代碼范例,它將會(huì)允許你提供并使用HeroService。此刻的代碼和最終代碼相差很大。

修改HeroesComponent

打開HeroesComponent類文件。

刪除HEROES的導(dǎo)入語句,因?yàn)槟阋院蟛粫?huì)再用它了。 轉(zhuǎn)而導(dǎo)入HeroService。

src/app/heroes/heroes.component.ts (import HeroService)

import{ HeroService } from?'../hero.service';

把heroes屬性的定義改為一句簡(jiǎn)單的聲明。

heroes: Hero[];

注入HeroService

往構(gòu)造函數(shù)中添加一個(gè)私有的heroService,其類型為HeroService。

constructor(privateheroService: HeroService) { }

這個(gè)參數(shù)同時(shí)做了兩件事:1. 聲明了一個(gè)私有heroService屬性,2. 把它標(biāo)記為一個(gè)HeroService的注入點(diǎn)。

當(dāng) Angular 創(chuàng)建HeroesComponent時(shí),依賴注入系統(tǒng)就會(huì)把這個(gè)heroService參數(shù)設(shè)置為HeroService的單例對(duì)象。

添加getHeroes()

創(chuàng)建一個(gè)函數(shù),以從服務(wù)中獲取這些英雄數(shù)據(jù)。

getHeroes():?void{

??this.heroes =?this.heroService.getHeroes();

}

在ngOnInit中調(diào)用它

你固然可以在構(gòu)造函數(shù)中調(diào)用getHeroes(),但那不是最佳實(shí)踐。

讓構(gòu)造函數(shù)保持簡(jiǎn)單,只做初始化操作,比如把構(gòu)造函數(shù)的參數(shù)賦值給屬性。 構(gòu)造函數(shù)不應(yīng)該做任何事。 它當(dāng)然不應(yīng)該調(diào)用某個(gè)函數(shù)來向遠(yuǎn)端服務(wù)(比如真實(shí)的數(shù)據(jù)服務(wù))發(fā)起 HTTP 請(qǐng)求。

而是選擇在 ngOnInit 生命周期鉤子中調(diào)用 getHeroes(),之后交由 Angular 處理,它會(huì)在構(gòu)造出 HeroesComponent 的實(shí)例之后的某個(gè)合適的時(shí)機(jī)調(diào)用 ngOnInit。

ngOnInit() {

??this.getHeroes();

}

查看運(yùn)行效果

刷新瀏覽器,該應(yīng)用仍運(yùn)行的一如既往。 顯示英雄列表,并且當(dāng)你點(diǎn)擊某個(gè)英雄的名字時(shí)顯示出英雄詳情視圖。

可觀察(Observable)的數(shù)據(jù)

HeroService.getHeroes()的函數(shù)簽名是同步的,它所隱含的假設(shè)是HeroService總是能同步獲取英雄列表數(shù)據(jù)。 而HeroesComponent也同樣假設(shè)能同步取到getHeroes()的結(jié)果。

this.heroes =?this.heroService.getHeroes();

這在真實(shí)的應(yīng)用中幾乎是不可能的。 現(xiàn)在能這么做,只是因?yàn)槟壳霸摲?wù)返回的是模擬數(shù)據(jù)。 不過很快,該應(yīng)用就要從遠(yuǎn)端服務(wù)器獲取英雄數(shù)據(jù)了,而那天生就是異步操作。

HeroService必須等服務(wù)器給出響應(yīng), 而getHeroes()不能立即返回英雄數(shù)據(jù), 瀏覽器也不會(huì)在該服務(wù)等待期間停止響應(yīng)。

HeroService.getHeroes()必須具有某種形式的異步函數(shù)簽名

它可以使用回調(diào)函數(shù),可以返回Promise(承諾),也可以返回Observable(可觀察對(duì)象)。

這節(jié)課,HeroService.getHeroes()將會(huì)返回Observable,因?yàn)樗罱K會(huì)使用 Angular 的HttpClient.get方法來獲取英雄數(shù)據(jù),而HttpClient.get()會(huì)返回Observable

可觀察對(duì)象版本的HeroService

Observable是RxJS 庫中的一個(gè)關(guān)鍵類。

稍后的 HTTP 教程中,你就會(huì)知道 AngularHttpClient的方法會(huì)返回 RxJS 的Observable。 這節(jié)課,你將使用 RxJS 的of()函數(shù)來模擬從服務(wù)器返回?cái)?shù)據(jù)。

打開HeroService文件,并從 RxJS 中導(dǎo)入Observable和of符號(hào)。

src/app/hero.service.ts (Observable imports)

import{ Observable, of } from?'rxjs';

把getHeroes方法改成這樣:

getHeroes(): Observable<Hero[]> {

??returnof(HEROES);

}

of(HEROES)會(huì)返回一個(gè)Observable<Hero[]>,它會(huì)發(fā)出單個(gè)值,這個(gè)值就是這些模擬英雄的數(shù)組。

HTTP 教程中,你將會(huì)調(diào)用HttpClient.get<Hero[]>()它也同樣返回一個(gè)Observable<Hero[]>,它也會(huì)發(fā)出單個(gè)值,這個(gè)值就是來自 HTTP 響應(yīng)體中的英雄數(shù)組。

在HeroesComponent中訂閱

HeroService.getHeroes方法之前返回一個(gè)Hero[], 現(xiàn)在它返回的是Observable<Hero[]>。

你必須在HeroesComponent中也向本服務(wù)中的這種形式看齊。

找到getHeroes方法,并且把它替換為如下代碼(和前一個(gè)版本對(duì)比顯示):

heroes.component.ts (Observable)

getHeroes():?void{

??this.heroService.getHeroes()

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

}

heroes.component.ts (Original)

getHeroes():?void{

??this.heroes =?this.heroService.getHeroes();

}

Observable.subscribe()是關(guān)鍵的差異點(diǎn)。

上一個(gè)版本把英雄的數(shù)組賦值給了該組件的heroes屬性。 這種賦值是同步的,這里包含的假設(shè)是服務(wù)器能立即返回英雄數(shù)組或者瀏覽器能在等待服務(wù)器響應(yīng)時(shí)凍結(jié)界面。

當(dāng)HeroService真的向遠(yuǎn)端服務(wù)器發(fā)起請(qǐng)求時(shí),這種方式就行不通了。

新的版本等待Observable發(fā)出這個(gè)英雄數(shù)組,這可能立即發(fā)生,也可能會(huì)在幾分鐘之后。 然后,subscribe函數(shù)把這個(gè)英雄數(shù)組傳給這個(gè)回調(diào)函數(shù),該函數(shù)把英雄數(shù)組賦值給組件的heroes屬性。

使用這種異步方式,當(dāng)HeroService從遠(yuǎn)端服務(wù)器獲取英雄數(shù)據(jù)時(shí),就可以工作了。

顯示消息

在這一節(jié),你將

添加一個(gè)MessagesComponent,它在屏幕的底部顯示應(yīng)用中的消息。

創(chuàng)建一個(gè)可注入的、全應(yīng)用級(jí)別的MessageService,用于發(fā)送要顯示的消息。

把MessageService注入到HeroService中。

當(dāng)HeroService成功獲取了英雄數(shù)據(jù)時(shí)顯示一條消息。

創(chuàng)建MessagesComponent

使用 CLI 創(chuàng)建MessagesComponent。

ng generate component messages

CLI 在src/app/messages中創(chuàng)建了組件文件,并且把MessagesComponent聲明在了AppModule中。

修改AppComponent的模板來顯示所生成的MessagesComponent:

/src/app/app.component.html

<h1>{{title}}</h1>

<app-heroes></app-heroes>

<app-messages></app-messages>

你可以在頁面的底部看到來自的MessagesComponent的默認(rèn)內(nèi)容。

創(chuàng)建MessageService

使用 CLI 在src/app中創(chuàng)建MessageService。

ng generate service message

打開MessageService,并把它的內(nèi)容改成這樣:

/src/app/message.service.ts

import{ Injectable } from?'@angular/core';


@Injectable({

??providedIn:?'root',

})

export?classMessageService {

??messages: string[] = [];


??add(message: string) {

????this.messages.push(message);

??}


??clear() {

????this.messages = [];

??}

}

該服務(wù)對(duì)外暴露了它的messages緩存,以及兩個(gè)方法:add()方法往緩存中添加一條消息,clear()方法用于清空緩存。

把它注入到HeroService中

重新打開HeroService,并且導(dǎo)入MessageService。

/src/app/hero.service.ts (import MessageService)

import{ MessageService } from?'./message.service';

修改這個(gè)構(gòu)造函數(shù),添加一個(gè)私有的messageService屬性參數(shù)。 Angular 將會(huì)在創(chuàng)建HeroService時(shí)把MessageService的單例注入到這個(gè)屬性中。

constructor(privatemessageService: MessageService) { }

這是一個(gè)典型的“服務(wù)中的服務(wù)”場(chǎng)景: 你把MessageService注入到了HeroService中,而HeroService又被注入到了HeroesComponent中。

從HeroService中發(fā)送一條消息

修改getHeroes方法,在獲取到英雄數(shù)組時(shí)發(fā)送一條消息。

getHeroes(): Observable<Hero[]> {

??// TODO: send the message _after_ fetching the heroes

??this.messageService.add('HeroService: fetched heroes');

??returnof(HEROES);

}

從HeroService中顯示消息

MessagesComponent可以顯示所有消息, 包括當(dāng)HeroService獲取到英雄數(shù)據(jù)時(shí)發(fā)送的那條。

打開MessagesComponent,并且導(dǎo)入MessageService。

/src/app/messages/messages.component.ts (import MessageService)

import{ MessageService } from?'../message.service';

修改構(gòu)造函數(shù),添加一個(gè)public的messageService屬性。 Angular 將會(huì)在創(chuàng)建MessagesComponent的實(shí)例時(shí) 把MessageService的實(shí)例注入到這個(gè)屬性中。

constructor(publicmessageService: MessageService) {}

這個(gè)messageService屬性必須是公共屬性,因?yàn)槟銓?huì)在模板中綁定到它。

Angular 只會(huì)綁定到組件的公共屬性。

綁定到MessageService

把 CLI 生成的MessagesComponent的模板改成這樣:

src/app/messages/messages.component.html

<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>

這個(gè)模板直接綁定到了組件的messageService屬性上。

*ngIf只有在有消息時(shí)才會(huì)顯示消息區(qū)。

*ngFor用來在一系列<div>元素中展示消息列表。

Angular 的事件綁定把按鈕的click事件綁定到了MessageService.clear()。

當(dāng)你把最終代碼某一頁的內(nèi)容添加到messages.component.css中時(shí),這些消息會(huì)變得好看一些。

刷新瀏覽器,頁面顯示出了英雄列表。 滾動(dòng)到底部,就會(huì)在消息區(qū)看到來自HeroService的消息。 點(diǎn)擊“清空”按鈕,消息區(qū)不見了。

查看最終代碼

你的應(yīng)用應(yīng)該變成了這樣在線例子/下載范例。本頁所提及的代碼文件如下。

如果你想直接在?stackblitz 運(yùn)行本頁中的例子,請(qǐng)單擊鏈接: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

對(duì)應(yīng)的文件列表和代碼鏈接如下:

文件名源代碼

src/app/hero.service.tshttps://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/hero.service.ts

src/app/heroes/heroes.component.tshttps://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/heroes/heroes.component.ts

src/app/messages/messages.component.tshttps://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/messages/messages.component.ts

src/app/messages/messages.component.htmlhttps://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/messages/messages.component.html

src/app/messages/messages.component.csshttps://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/messages/messages.component.css

src/app/app.module.tshttps://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/app.module.ts

src/app/app.component.htmlhttps://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/app.component.html

小結(jié)

你把數(shù)據(jù)訪問邏輯重構(gòu)到了HeroService類中。

你在根注入器中把HeroService注冊(cè)為該服務(wù)的提供商,以便在別處可以注入它。

你使用Angular 依賴注入機(jī)制把它注入到了組件中。

你給HeroService中獲取數(shù)據(jù)的方法提供了一個(gè)異步的函數(shù)簽名。

你發(fā)現(xiàn)了Observable以及 RxJS 庫。

你使用 RxJS 的of()方法返回了一個(gè)模擬英雄數(shù)據(jù)的可觀察對(duì)象(Observable<Hero[]>)。

在組件的ngOnInit生命周期鉤子中調(diào)用HeroService方法,而不是構(gòu)造函數(shù)中。

你創(chuàng)建了一個(gè)MessageService,以便在類之間實(shí)現(xiàn)松耦合通訊。

HeroService連同注入到它的服務(wù)MessageService一起,注入到了組件中。


https://www.cwiki.us/display/AngularZH/Services

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 應(yīng)用程序現(xiàn)在有了基本的標(biāo)題。 接下來你要?jiǎng)?chuàng)建一個(gè)新的組件來顯示英雄信息并且把這個(gè)組件放到應(yīng)用程序的外殼里去。 創(chuàng)建...
    HoneyMoose閱讀 1,297評(píng)論 0 0
  • 此刻,HeroesComponent同時(shí)顯示了英雄列表和所選英雄的詳情。 把所有特性都放在同一個(gè)組件中,將會(huì)使應(yīng)用...
    HoneyMoose閱讀 329評(píng)論 0 0
  • 這篇完全就是個(gè)人筆記了,大概記了一下服務(wù),注入和管道的用法。 服務(wù) (Service) angular服務(wù)的主要目...
    啃香菜的花蘿蘿閱讀 659評(píng)論 0 2
  • 在本頁面,你將擴(kuò)展《英雄指南》應(yīng)用,讓它顯示一個(gè)英雄列表, 并允許用戶選擇一個(gè)英雄,查看該英雄的詳細(xì)信息。 創(chuàng)建模...
    HoneyMoose閱讀 642評(píng)論 0 0
  • AngularJS 提供很好的依賴注入機(jī)制。以下5個(gè)核心組件用來作為依賴注入: value factory ser...
    樗云閱讀 800評(píng)論 0 0

友情鏈接更多精彩內(nèi)容