隨着「英雄之旅」應用的發展,您將添加更多須要訪問英雄數據的組件。css
不是一遍又一遍複製和粘貼相同的代碼,而是建立一個可重用的數據服務,並將其注入到須要它的組件中。 使用單獨的服務可以使組件保持精簡併專一於支持視圖,並使用模擬服務對組件進行單元測試變得容易。html
由於數據服務老是異步的,因此您將使用數據服務的基於Future的版原本完成頁面。java
當你完成這個頁面,應用程序應該看起來像這個實例(查看源代碼)。git
在繼續英雄之旅以前,請確認您具備如下結構。 若是沒有,請返回前面的頁面。github
若是該應用程序還沒有運行,請啓動該應用程序。 在進行更改時,請經過從新加載瀏覽器窗口來保持運行。web
利益相關者但願以不一樣的頁面以各類方式展現英雄。 用戶能夠從列表中選擇一個英雄。 不久,您將添加一個儀表板與頂尖的表演英雄,並建立一個單獨的視圖編輯英雄的細節。 全部三個視圖都須要英雄數據。編程
目前,AppComponent定義了模擬英雄的顯示。 然而,定義英雄不是組件的工做,你不能輕易與其餘組件和視圖共享英雄名單。 在這個頁面中,您將把英雄數據採集業務轉移到一個提供數據的服務中,並與須要數據的全部組件共享該服務。api
在lib / src下建立文件hero_service.dart。瀏覽器
服務文件的命名約定是小寫的服務名稱,後跟_service。 對於多詞服務名稱,請使用小寫的snake_case。 例如,SpecialSuperHeroService的文件名是special_super_hero_service.dart。緩存
命名類HeroService。lib/src/hero_service.dart (empty class)
import 'package:angular/angular.dart'; @Injectable() class HeroService { }
注意你使用了@Injectable()註解。 這告訴Angular編譯器,HeroService將成爲注入的候選者(更多關於這個)。
HeroService能夠從任何地方(Web服務,本地存儲或模擬數據源)獲取英雄數據。 如今,導入Hero和mockHeroes,並從getHeroes()方法返回模擬英雄:lib/src/hero_service.dart
import 'package:angular/angular.dart'; import 'hero.dart'; import 'mock_heroes.dart'; @Injectable() class HeroService { List<Hero> getHeroes() => mockHeroes; }
您已經準備好在其餘組件中使用HeroService,從AppComponent開始。
導入HeroService,以便您能夠在代碼中引用它。lib/app_component.dart (hero service import)
import 'src/hero_service.dart';
不要使用new實例化HeroService
AppComponent應該如何獲取HeroService的實例?
你可能會像這樣建一個HeroService的新實例:lib/app_component.dart (excerpt)
HeroService heroService = new HeroService(); // DON'T do this
可是,這個選項並不理想,緣由以下:
注入HeroService
而不是使用新的表達式,添加這些行:
這裏是屬性和構造函數:lib/app_component.dart (constructor)
final HeroService _heroService; AppComponent(this._heroService);
構造函數除了設置_heroService屬性外什麼也不作。 _heroService的HeroService類型將構造函數的參數標識爲HeroService注入點。
如今Angular知道在建立一個新的AppComponent時要提供一個HeroService實例。
在依賴注入頁面閱讀更多關於依賴注入的內容。
注入器不知道如何建立一個HeroService。 若是您如今運行代碼,Angular會失敗並顯示如下錯誤:
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
爲了教導注入器如何建立HeroService,請添加如下提供程序列表做爲@Component註解的最後一個參數。lib/app_component.dart (providers)
providers: const [HeroService],
providers參數告訴Angular在建立一個AppComponent時建立一個HeroService的新實例。 AppComponent及其子組件可使用該服務來獲取英雄數據。
添加一個getHeroes()方法到應用程序組件,並刪除英雄初始值設定項:lib/app_component.dart (heroes and getHeroes)
List<Hero> heroes; void getHeroes() { heroes = _heroService.getHeroes(); }
AppComponent應該能夠獲取並顯示英雄數據,而不會出現問題。
您可能會試圖在構造函數中調用getHeroes()方法,但構造函數不該包含複雜的邏輯,特別是調用服務器的構造函數(如數據訪問方法)。 構造函數用於簡單的初始化,如將構造函數參數鏈接到屬性。
要用Angular調用getHeroes(),能夠實現Angular ngOnInit生命週期鉤子。 Angular爲組件生命週期中的關鍵時刻提供接口:建立,每次更改以後,最終銷燬。
每一個接口都有一個方法。 當組件實現該方法時,Angular會在適當的時候調用它。
在「Lifecycle Hooks」頁面中詳細瞭解生命週期掛鉤。
將OnInit添加到由AppComponent實現的接口列表中,並使用裏面的初始化邏輯編寫一個ngOnInit()方法。 Angular會在正確的時間調用它。 在這種狀況下,經過調用getHeroes()來初始化。
class AppComponent implements OnInit { void ngOnInit() => getHeroes(); }
刷新瀏覽器。 當你點擊一個英雄名字時,應用程序應該顯示英雄名單和英雄詳情視圖。
HeroService當即返回模擬英雄列表; 它的getHeroes()簽名是同步的。
lib/src/hero_service.dart (getHeroes)
List<Hero> getHeroes() => mockHeroes;
最終,英雄數據未來自遠程服務器。 當使用遠程服務器時,用戶沒必要等待服務器響應; 此外,您在等待期間沒法阻塞用戶界面。
爲了協調視圖和響應,你可使用Futures,這是一個改變getHeroes()方法簽名的異步技術。
Future表明將來的計算或值。 使用Future,您能夠註冊回調函數,在計算完成時(結果準備就緒),或須要報告計算錯誤時調用。
這是一個簡單的解釋。 在「Asynchronous Programming: Futures」的Dart語言教程中閱讀更多有關Futures的信息。
添加dart:async的導入,由於它定義了Future,並使用這個Future返回的getHeroes()方法更新HeroService:lib/src/hero_service.dart (excerpt)
Future<List<Hero>> getHeroes() async => mockHeroes;
你還在模擬數據。 你正在模擬一個超快,零延遲的服務器的行爲,經過返回一個模擬英雄當即可用的Future。
將方法標記爲async會自動將返回類型設置爲Future。 有關異步函數的更多信息,請參閱在Dart語言瀏覽中聲明異步函數。
因爲對HeroService的更改,應用程序組件的英雄屬性如今是Future,而不是英雄列表。 您必須更改實現以在完成時處理Future結果。 當Future成功完成時,您將顯示英雄。
這是當前的實現:lib/app_component.dart (synchronous getHeroes)
void getHeroes() { heroes = _heroService.getHeroes(); }
將回調函數做爲參數傳遞給Future.then()方法:lib/app_component.dart (asynchronous getHeroes)
void getHeroes() { _heroService.getHeroes().then((heroes) => this.heroes = heroes); }
該回調將組件的英雄屬性設置爲服務返回的英雄列表。刷新瀏覽器。 該應用程序仍然運行,顯示英雄列表,並響應名稱選擇與詳細信息視圖。
包含一個或多個Future.then()方法的異步方法可能難以閱讀和理解。 謝天謝地,Dart的異步/等待語言功能可讓你編寫看起來就像同步代碼的異步代碼。 重寫getHeroes():lib/app_component.dart (revised async/await getHeroes)
Future<Null> getHeroes() async { heroes = await _heroService.getHeroes(); }
Future <Null>返回類型是異步void的等價物。
在Dart語言教程的Asynchronous Programming:Futures的Async和await部分閱讀更多關於使用async / await進行異步編程的內容。
在本頁的末尾, Appendix: Take it slow描述應用程序可能與不良鏈接相似。
在全部重構以後驗證您是否具備如下結構:
這裏是本頁討論的代碼文件。
lib/src/hero_service.dart
import 'dart:async'; import 'package:angular/angular.dart'; import 'hero.dart'; import 'mock_heroes.dart'; @Injectable() class HeroService { Future<List<Hero>> getHeroes() async => mockHeroes; }
lib/app_component.dart
import 'dart:async'; import 'package:angular/angular.dart'; import 'src/hero.dart'; import 'src/hero_detail_component.dart'; import 'src/hero_service.dart'; @Component( selector: 'my-app', templateUrl: 'app_component.html', styleUrls: const ['app_component.css'], directives: const [CORE_DIRECTIVES, HeroDetailComponent], providers: const [HeroService], ) class AppComponent implements OnInit { final title = 'Tour of Heroes'; final HeroService _heroService; List<Hero> heroes; Hero selectedHero; AppComponent(this._heroService); Future<Null> getHeroes() async { heroes = await _heroService.getHeroes(); } void ngOnInit() => getHeroes(); void onSelect(Hero hero) => selectedHero = hero; }
如下是您在此頁面中所取得的成果:
英雄之旅已經變得更加可重複使用共享組件和服務。 下一個目標是建立一個儀表板,添加在視圖之間路由的菜單連接,以及在模板中格式化數據。 隨着應用程序的發展,你會發現如何設計它,使其更容易成長和維護。
閱讀下一個教程頁面中有關Angular組件路由器和視圖之間的導航。
要模擬一個緩慢的鏈接,請將如下getHeroesSlowly()方法添加到HeroService。
lib/src/hero_service.dart (getHeroesSlowly)
Future<List<Hero>> getHeroesSlowly() { return new Future.delayed(const Duration(seconds: 2), getHeroes); }
像getHeroes()同樣,它也返回一個Future,可是這個Future在完成前等待兩秒鐘。
回到AppComponent中,用getHeroesSlowly()替換getHeroes(),看看應用程序的行爲。