版本:4.0.0+2
在本章,你會做以下改進(jìn)。
- 從一個服務(wù)器獲取英雄數(shù)據(jù)篮灼。
- 讓用戶添加、編輯和刪除英雄徘禁。
- 保存改變到服務(wù)器诅诱。
你會教給應(yīng)用發(fā)起到一個遠(yuǎn)程服務(wù)器的 web API 的相應(yīng)的 HTTP 請求。
當(dāng)你按照本章做完這一切送朱,應(yīng)用看起來應(yīng)該這樣——在線示例 (查看源碼)娘荡。
你離開的地方
在前一章中,你學(xué)會了在儀表盤和固定的英雄列表之間導(dǎo)航驶沼,并編輯選定的英雄炮沐。這也就是本章的起點(diǎn)。
在繼續(xù)英雄指南之前回怜,驗(yàn)證你是否有如下結(jié)構(gòu)大年。
angular_tour_of_heroes/
|___lib/
| |___app_component.{css,dart}
|___src/
| | |___dashboard_component.{css,dart,html}
| | |___hero.dart
| | |___hero_detail_component.{css,dart,html}
| | |___hero_service.dart
| | |___heroes_component.{css,dart,html}
| | |___mock_heroes.dart
|___test/
| |___app_test.dart
| |___...
|___web/
| |___index.html
| |___main.dart
| |___styles.css
|___pubspec.yaml
如果應(yīng)用還沒有運(yùn)行,使用pub serve
啟動應(yīng)用玉雾。當(dāng)你做出改變時翔试,通過刷新瀏覽器窗口,使其保持運(yùn)行复旬。
提供 HTTP 服務(wù)
你將使用 Dart的 http 包的客戶端類來與服務(wù)器通信垦缅。
更新 Pubspec
通過添加Dart http 和 stream_transform 包來更新包依賴。
// {toh-5 → toh-6}/pubspec.yaml
dependencies:
angular: ^4.0.0
angular_forms: ^1.0.0
angular_router: ^1.0.2
+ http: ^0.11.0
+ stream_transform: ^0.0.6
注冊 HTTP 服務(wù)
在應(yīng)用可以使用BrowserClient
之前赢底,你必須把它當(dāng)一個服務(wù)提供器注冊失都。
你在應(yīng)用的任何地方都應(yīng)該能訪問BrowserClient
服務(wù)。因此在bootstrap
調(diào)用中注冊它幸冻,那是你啟動應(yīng)用和根組件AppComponent
的地方。
// web/main.dart (v1)
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_tour_of_heroes/app_component.dart';
import 'package:http/browser_client.dart';
void main() {
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
// Remove next line in production
provide(LocationStrategy, useClass: HashLocationStrategy),
provide(BrowserClient, useFactory: () => new BrowserClient(), deps: [])
]);
}
注意咳焚,你在bootstrap
方法的第二個參數(shù)列表中提供BrowserClient
洽损。這和在@Component
注解中的providers
列表中有同樣的效果。
注意:除非你有一個適當(dāng)配置的后端服務(wù)器(或一個模擬服務(wù)器)革半,否則碑定,這個應(yīng)用并不工作流码。接下來的部分展示如何與一個后端服務(wù)器模擬交互。
模擬 Web API
在你有一個處理英雄數(shù)據(jù)請求的 Web 服務(wù)器之前延刘,HTTP 客戶端會從一個模擬服務(wù)器——內(nèi)存 web API 獲取并保存數(shù)據(jù)漫试。
使用模擬服務(wù)器的版本來更新web/main.dart
:
// web/main.dart (v2)
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_tour_of_heroes/app_component.dart';
import 'package:angular_tour_of_heroes/in_memory_data_service.dart';
import 'package:http/http.dart';
void main() {
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
// Remove next line in production
provide(LocationStrategy, useClass: HashLocationStrategy),
provide(Client, useClass: InMemoryDataService),
// Using a real back end?
// Import browser_client.dart and change the above to:
// [provide(Client, useFactory: () => new BrowserClient(), deps: [])]
]);
}
你想要使用內(nèi)存 Web API 服務(wù)代替BrowserClient
,與遠(yuǎn)程服務(wù)器進(jìn)行會話碘赖。內(nèi)存 Web API 服務(wù)如下所示驾荣,是通過http
庫的MockClient
類來實(shí)現(xiàn)的。所有的http
客戶端實(shí)現(xiàn)都共享了一個通用的Client
接口普泡。所以你要在應(yīng)用中使用Client
類型播掷,以便在各個實(shí)現(xiàn)之間自由地切換。
// lib/in_memory_data_service.dart (init)
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:angular/angular.dart';
import 'package:http/http.dart';
import 'package:http/testing.dart';
import 'src/hero.dart';
@Injectable()
class InMemoryDataService extends MockClient {
static final _initialHeroes = [
{'id': 11, 'name': 'Mr. Nice'},
{'id': 12, 'name': 'Narco'},
{'id': 13, 'name': 'Bombasto'},
{'id': 14, 'name': 'Celeritas'},
{'id': 15, 'name': 'Magneta'},
{'id': 16, 'name': 'RubberMan'},
{'id': 17, 'name': 'Dynama'},
{'id': 18, 'name': 'Dr IQ'},
{'id': 19, 'name': 'Magma'},
{'id': 20, 'name': 'Tornado'}
];
static List<Hero> _heroesDb;
static int _nextId;
static Future<Response> _handler(Request request) async {
if (_heroesDb == null) resetDb();
var data;
switch (request.method) {
case 'GET':
final id =
int.parse(request.url.pathSegments.last, onError: (_) => null);
if (id != null) {
data = _heroesDb
.firstWhere((hero) => hero.id == id); // throws if no match
} else {
String prefix = request.url.queryParameters['name'] ?? '';
final regExp = new RegExp(prefix, caseSensitive: false);
data = _heroesDb.where((hero) => hero.name.contains(regExp)).toList();
}
break;
case 'POST':
var name = JSON.decode(request.body)['name'];
var newHero = new Hero(_nextId++, name);
_heroesDb.add(newHero);
data = newHero;
break;
case 'PUT':
var heroChanges = new Hero.fromJson(JSON.decode(request.body));
var targetHero = _heroesDb.firstWhere((h) => h.id == heroChanges.id);
targetHero.name = heroChanges.name;
data = targetHero;
break;
case 'DELETE':
var id = int.parse(request.url.pathSegments.last);
_heroesDb.removeWhere((hero) => hero.id == id);
// No data, so leave it as null.
break;
default:
throw 'Unimplemented HTTP method ${request.method}';
}
return new Response(JSON.encode({'data': data}), 200,
headers: {'content-type': 'application/json'});
}
static resetDb() {
_heroesDb = _initialHeroes.map((json) => new Hero.fromJson(json)).toList();
_nextId = _heroesDb.map((hero) => hero.id).fold(0, max) + 1;
}
static String lookUpName(int id) =>
_heroesDb.firstWhere((hero) => hero.id == id, orElse: null)?.name;
InMemoryDataService() : super(_handler);
}
這個文件代替了mock_heroes.dart
撼班,它現(xiàn)在可以安全的刪除了歧匈。
作為通用的 Web API 服務(wù),模擬內(nèi)存服務(wù)將以 JSON 格式進(jìn)行編碼和解碼英雄砰嘁,所以給Hero
類增加這些功能:
// lib/src/hero.dart
class Hero {
final int id;
String name;
Hero(this.id, this.name);
factory Hero.fromJson(Map<String, dynamic> hero) =>
new Hero(_toInt(hero['id']), hero['name']);
Map toJson() => {'id': id, 'name': name};
}
int _toInt(id) => id is int ? id : int.parse(id);
英雄與 HTTP
在當(dāng)前的HeroService
的實(shí)現(xiàn)中件炉,返回一個 Future 類型的模擬英雄。
Future<List<Hero>> getHeroes() async => mockHeroes;
這是最終使用 HTTP 客戶端來獲取英雄的預(yù)期實(shí)現(xiàn)矮湘,那一定是一個異步操作斟冕。
現(xiàn)在將getHeroes()
轉(zhuǎn)換為使用 HTTP 的形式 。
// lib/src/hero_service.dart (updated getHeroes and new class members)
static const _heroesUrl = 'api/heroes'; // URL to web API
final Client _http;
HeroService(this._http);
Future<List<Hero>> getHeroes() async {
try {
final response = await _http.get(_heroesUrl);
final heroes = _extractData(response)
.map((value) => new Hero.fromJson(value))
.toList();
return heroes;
} catch (e) {
throw _handleError(e);
}
}
dynamic _extractData(Response resp) => JSON.decode(resp.body)['data'];
Exception _handleError(dynamic e) {
print(e); // for demo purposes only
return new Exception('Server error; cause: $e');
}
更新導(dǎo)入語句如下:
// lib/src/hero_service.dart (updated imports)
import 'dart:async';
import 'dart:convert';
import 'package:angular/angular.dart';
import 'package:http/http.dart';
import 'hero.dart';
刷新瀏覽器板祝。英雄數(shù)據(jù)應(yīng)該從模擬服務(wù)器中成功加載宫静。
HTTP Future
為獲取到英雄列表,首先異步調(diào)用http.get()
券时。然后使用_extractData
助手方法來解碼響應(yīng)的正文孤里。
響應(yīng)的 JSON 有一個單一的data
屬性,保存了調(diào)用者想要的英雄列表橘洞。所以提取這個列表捌袜,并將它作為解析后的 Future 值返回。
注意這個由服務(wù)器返回的數(shù)據(jù)的形態(tài)炸枣。這個特殊的內(nèi)存 Web API 的實(shí)例返回一個帶有
data
屬性的對象虏等。你的 API 可能返回其它東西。調(diào)整代碼以匹配你的 Web API适肠。
調(diào)用者并不知道從(模擬)服務(wù)器獲取英雄霍衫。和以前一樣它接收一個英雄的 Future。
錯誤處理
在getHeroes()
的最后侯养,你catch
了服務(wù)器的失敗信息敦跌,并把它們傳給了錯誤處理器:
} catch (e) {
throw _handleError(e);
}
這是一個關(guān)鍵的步驟!你必須預(yù)料到 HTTP 請求會失敗逛揩,因?yàn)橛刑喑隹刂频脑蚩赡軐?dǎo)致它們頻繁發(fā)生柠傍。
Exception _handleError(dynamic e) {
print(e); // for demo purposes only
return new Exception('Server error; cause: $e');
}
這個示例服務(wù)把錯誤記錄到控制臺中麸俘;在真實(shí)世界中,你應(yīng)該在代碼中處理錯誤惧笛。作為一個展示从媚,它能工作就夠了。
這個代碼還包含一個通過傳播異常給調(diào)用者的錯誤患整,以便調(diào)用者可以給用戶顯示恰當(dāng)?shù)腻e誤信息拜效。
通過 id 獲取英雄
當(dāng)HeroDetailComponent
請求HeroService
來獲取一個英雄時,HeroService
當(dāng)前獲取所有英雄并通過匹配id
來過濾這一個并级。對于一個模擬環(huán)境是很好的拂檩,但當(dāng)你只想要一個時,卻向真實(shí)的服務(wù)器請求所有的英雄是浪費(fèi)的嘲碧。多數(shù)的 web APIs 支持這樣的格式api/hero/:id
(例如api/hero/11
)的 get-by-id 請求稻励。
使用 get-by-id 請求更新HeroService.getHero()
方法:
// lib/src/hero_service.dart (getHero)
Future<Hero> getHero(int id) async {
try {
final response = await _http.get('$_heroesUrl/$id');
return new Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
}
這個請求和getHeroes()
幾乎一樣。在 URL中的英雄 id 標(biāo)識服務(wù)器應(yīng)該更新哪個英雄愈涩。
另外望抽,響應(yīng)中的data
是一個單一的英雄對象而不是一個列表。
未改變的 getHeroes API
盡管你對getHeroes()
和getHero()
做了一些重要的內(nèi)部修改履婉,但公共簽名并沒有改變煤篙。從這兩個方法仍然返回一個 Future。你不需要更新任何調(diào)用了它們的組件毁腿。
現(xiàn)在是時候添加創(chuàng)建和刪除英雄的功能了辑奈。
更新英雄詳情
嘗試在英雄詳情視圖編輯一個英雄的名字了。隨著你的輸入已烤,英雄名字也會隨之在視圖的標(biāo)題更新鸠窗。但如果你點(diǎn)擊了 Back(后退)按鈕,這些修改就丟失了胯究。
之前更新是不會丟失的稍计。有什么被改變了?當(dāng)應(yīng)用使用模擬英雄列表時裕循,更新直接被應(yīng)用到了單一的臣嚣、全應(yīng)用范圍共享的列表中的英雄對象。現(xiàn)在你從一個服務(wù)器獲取數(shù)據(jù)剥哑,如果你想要保存這些更改硅则,你必須把它們寫回到服務(wù)器。
添加保存英雄詳情的功能
在英雄詳情模板的結(jié)尾添加一個帶click
事件綁定的保存按鈕株婴,事件綁定會調(diào)用組件中一個名為save()
的新方法抢埋。
// lib/src/hero_detail_component.html (save)
<button (click)="save()">Save</button>
添加下面的save()
方法,它使用英雄服務(wù)的update()
方法來保存英雄名的改變督暂,然后導(dǎo)航回前一個視圖揪垄。
// lib/src/hero_detail_component.dart (save)
Future<Null> save() async {
await _heroService.update(hero);
goBack();
}
給英雄服務(wù)添加 update() 方法
update()
方法的總體結(jié)構(gòu)和getHeroes()
很相似,但它使用 HTTPput()
來把修改保存到服務(wù)器端逻翁。
// lib/src/hero_service.dart (update)
static final _headers = {'Content-Type': 'application/json'};
Future<Hero> update(Hero hero) async {
try {
final url = '$_heroesUrl/${hero.id}';
final response =
await _http.put(url, headers: _headers, body: JSON.encode(hero));
return new Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
}
為了確定服務(wù)器應(yīng)該更新哪個英雄饥努,英雄id
被編碼進(jìn) URL 中。putt()
body 參數(shù)是通過調(diào)用JSON.encode
獲得的英雄的 JSON 字符串編碼八回。body 的內(nèi)容類型(application/json
)被標(biāo)記在請求頭中酷愧。
刷新瀏覽器,改變一個英雄的名字缠诅,保存你的修改溶浴,并點(diǎn)擊瀏覽器的后退按鈕。修改現(xiàn)在應(yīng)該保存了管引。
增加添加英雄的功能
要添加一個英雄士败,應(yīng)用需要這個英雄的名字。你可以使用一個input
元素搭配一個添加按鈕褥伴。
在 heroes 組件的 HTML 中谅将,緊跟標(biāo)題的后面,插入如下內(nèi)容:
// lib/src/heroes_component.html (add)
<div>
<label>Hero name:</label> <input #heroName />
<button (click)="add(heroName.value); heroName.value=''">
Add
</button>
</div>
響應(yīng)一個點(diǎn)擊事件重慢,調(diào)用組件的點(diǎn)擊處理器饥臂,然后清空這個輸入框,以便準(zhǔn)備好輸入另一個名字似踱。
// lib/src/heroes_component.dart (add)
Future<Null> add(String name) async {
name = name.trim();
if (name.isEmpty) return;
heroes.add(await _heroService.create(name));
selectedHero = null;
}
當(dāng)給定的名字不為空時隅熙,處理器委托英雄服務(wù)來創(chuàng)建這個命名英雄,然后把這個新英雄添加到列表中核芽。
在HeroService
類中實(shí)現(xiàn)這個create()
方法囚戚。
// lib/src/hero_service.dart (create)
Future<Hero> create(String name) async {
try {
final response = await _http.post(_heroesUrl,
headers: _headers, body: JSON.encode({'name': name}));
return new Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
}
刷新瀏覽器,并創(chuàng)建一些英雄狞洋。
增加刪除英雄的功能
在英雄視圖中的每個英雄都應(yīng)該有一個刪除按鈕弯淘。
把下面的按鈕元素添加到 heroes 組件的 HTML 中,把它放在重復(fù)的<li>
元素里英雄名的后面吉懊。
<button class="delete"
(click)="delete(hero); $event.stopPropagation()">x</button>
<li>
元素應(yīng)該看起來像這樣:
// lib/src/heroes_component.html (li element)
<li *ngFor="let hero of heroes" (click)="onSelect(hero)"
[class.selected]="hero === selectedHero">
<span class="badge">{{hero.id}}</span>
<span>{{hero.name}}</span>
<button class="delete"
(click)="delete(hero); $event.stopPropagation()">x</button>
</li>
除了調(diào)用組件的delete()
方法庐橙,這個刪除按鈕的點(diǎn)擊處理器代碼阻止點(diǎn)擊事件冒泡——你并不希望<li>
的點(diǎn)擊事件處理器被觸發(fā),因?yàn)檫@樣做想要選擇英雄時用戶會刪除這個英雄借嗽。
delete()
處理器的邏輯有點(diǎn)棘手:
// lib/src/heroes_component.dart (delete)
Future<Null> delete(Hero hero) async {
await _heroService.delete(hero.id);
heroes.remove(hero);
if (selectedHero == hero) selectedHero = null;
}
你當(dāng)然委托給英雄服務(wù)來刪除英雄态鳖,但該組件仍然負(fù)責(zé)更新顯示:如果有必要,它從列表中移除 已刪除的英雄恶导,并重置所選英雄浆竭。
添加如下 CSS 把刪除按鈕放在英雄條目的最右邊:
//
lib/src/heroes_component.css (additions)
button.delete {
float:right;
margin-top: 2px;
margin-right: .8em;
background-color: gray !important;
color:white;
}
英雄服務(wù)的 delete() 方法
添加英雄服務(wù)的delete()
方法,使用 HTTP 的delete()
方法從服務(wù)器上移除英雄:
// lib/src/hero_service.dart (delete)
Future<Null> delete(int id) async {
try {
final url = '$_heroesUrl/$id';
await _http.delete(url, headers: _headers);
} catch (e) {
throw _handleError(e);
}
}
刷新瀏覽器,并試試這個新的刪除功能邦泄。
數(shù)據(jù)流
回想一下删窒,HeroService.getHeroes()
等待一個http.get()
響應(yīng),并生成一個List<Hero>
的 Future顺囊。當(dāng)你只對一個單一的結(jié)果感興趣時肌索,這很不錯。
但是請求并不總是只發(fā)起一次特碳。你可能開始發(fā)起一個請求诚亚,然后取消,并在服務(wù)器對第一個請求作出響應(yīng)前發(fā)起一個不同的請求午乓。一個請求-取消-新請求的序列難以通過Futures 實(shí)現(xiàn)站宗,但使用 Streams 卻很容易。
增加按名搜索的功能
你將為英雄指南添加一個英雄搜索 的特性益愈。當(dāng)用戶在搜索框中輸入名字時梢灭,你會重復(fù)發(fā)起根據(jù)名字過濾英雄的 HTTP 請求棍苹。
先創(chuàng)建HeroSearchService
服務(wù)惜互,它會把搜索查詢發(fā)送到服務(wù)器的 Web API。
// lib/src/hero_search_service.dart
import 'dart:async';
import 'dart:convert';
import 'package:angular/angular.dart';
import 'package:http/http.dart';
import 'hero.dart';
@Injectable()
class HeroSearchService {
final Client _http;
HeroSearchService(this._http);
Future<List<Hero>> search(String term) async {
try {
final response = await _http.get('app/heroes/?name=$term');
return _extractData(response)
.map((json) => new Hero.fromJson(json))
.toList();
} catch (e) {
throw _handleError(e);
}
}
dynamic _extractData(Response resp) => JSON.decode(resp.body)['data'];
Exception _handleError(dynamic e) {
print(e); // for demo purposes only
return new Exception('Server error; cause: $e');
}
}
HeroSearchService
中的_http.get()
調(diào)用和HeroService
中的那一個類似橙依,盡管現(xiàn)在這個 URL 帶了查詢字符串枣接。
HeroSearchComponent
創(chuàng)建一個HeroSearchComponent
颂暇,它調(diào)用這個新的HeroSearchService
。
組件模板很簡單但惶,就是一個輸入框和一個相匹配的搜索結(jié)果列表耳鸯。
// lib/src/hero_search_component.html
<div id="search-component">
<h4>Hero Search</h4>
<input #searchBox id="search-box"
(change)="search(searchBox.value)"
(keyup)="search(searchBox.value)" />
<div>
<div *ngFor="let hero of heroes | async"
(click)="gotoDetail(hero)" class="search-result" >
{{hero.name}}
</div>
</div>
</div>
同時,給這個新組件添加樣式膀曾。
// lib/src/hero_search_component.css
.search-result {
border-bottom: 1px solid gray;
border-left: 1px solid gray;
border-right: 1px solid gray;
width:195px;
height: 20px;
padding: 5px;
background-color: white;
cursor: pointer;
}
#search-box {
width: 200px;
height: 20px;
}
當(dāng)用戶在搜索框中輸入時县爬,一個 keyup 事件綁定調(diào)用該組件的search()
方法,并傳入新的搜索框的值添谊。如果用戶使用鼠標(biāo)粘貼文本财喳,change 事件綁定會被觸發(fā)。
不出所料斩狱,*ngFor
從該組件的heroes
屬性重復(fù) hero 對象耳高。
但你很快就會看到,現(xiàn)在heroes
屬性是一個英雄列表的 Stream所踊,而不再只是一個英雄列表泌枪。*ngFor
不能使用Stream
做任何事,除非你通過async
管道(AsyncPipe
)聯(lián)通它秕岛。async
管道訂閱Stream
碌燕,并為*ngFor
生成一個英雄列表误证。
創(chuàng)建HeroSearchComponent
類及其元數(shù)據(jù)。
// lib/src/hero_search_component.dart
import 'dart:async';
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:stream_transform/stream_transform.dart';
import 'hero_search_service.dart';
import 'hero.dart';
@Component(
selector: 'hero-search',
templateUrl: 'hero_search_component.html',
styleUrls: const ['hero_search_component.css'],
directives: const [CORE_DIRECTIVES],
providers: const [HeroSearchService],
pipes: const [COMMON_PIPES],
)
class HeroSearchComponent implements OnInit {
HeroSearchService _heroSearchService;
Router _router;
Stream<List<Hero>> heroes;
StreamController<String> _searchTerms =
new StreamController<String>.broadcast();
HeroSearchComponent(this._heroSearchService, this._router) {}
// Push a search term into the stream.
void search(String term) => _searchTerms.add(term);
Future<Null> ngOnInit() async {
heroes = _searchTerms.stream
.transform(debounce(new Duration(milliseconds: 300)))
.distinct()
.transform(switchMap((term) => term.isEmpty
? new Stream<List<Hero>>.fromIterable([<Hero>[]])
: _heroSearchService.search(term).asStream()))
.handleError((e) {
print(e); // for demo purposes only
});
}
void gotoDetail(Hero hero) {
var link = [
'HeroDetail',
{'id': hero.id.toString()}
];
_router.navigate(link);
}
}
搜索條目
聚焦_searchTerms
:
StreamController<String> _searchTerms =
new StreamController<String>.broadcast();
// Push a search term into the stream.
void search(String term) => _searchTerms.add(term);
StreamController修壕,顧名思義愈捅,是一個 Stream 的控制器,比如它允許你通過給它添加數(shù)據(jù)來操作潛在的數(shù)據(jù)流叠殷。
在這個例子中改鲫,字符串的潛在的數(shù)據(jù)流(_searchTerms.stream
),表示與用戶輸入的搜索詞匹配的英雄名林束。每次調(diào)用search()
,都會通過調(diào)用控制器的add()
稽亏,把新的字符串放進(jìn)數(shù)據(jù)流壶冒。
初始化 heroes 屬性(ngOnInit)
你可以把搜索條目的數(shù)據(jù)流轉(zhuǎn)換成Hero
列表的數(shù)據(jù)流,并把結(jié)果賦值給heroes
屬性截歉。
Stream<List<Hero>> heroes;
Future<Null> ngOnInit() async {
heroes = _searchTerms.stream
.transform(debounce(new Duration(milliseconds: 300)))
.distinct()
.transform(switchMap((term) => term.isEmpty
? new Stream<List<Hero>>.fromIterable([<Hero>[]])
: _heroSearchService.search(term).asStream()))
.handleError((e) {
print(e); // for demo purposes only
});
}
每次用戶按鍵都立刻傳給HeroSearchService
胖腾,就會創(chuàng)建海量的 HTTP 請求,浪費(fèi)服務(wù)器資源并消耗大量網(wǎng)絡(luò)流量瘪松。
相反咸作,你可以使用鏈?zhǔn)秸{(diào)用Stream
操作符,減少流向字符串Stream
的請求宵睦。你將發(fā)起較少的HeroSearchService
調(diào)用记罚,并且仍然及時地獲得結(jié)果。做法如下:
-
transform(debounce(... 300)))
:在傳遞最終的字符串之前壳嚎,會一直等待桐智,直到搜索條目暫停輸入300毫秒。你發(fā)起請求的頻率永遠(yuǎn)不會超過300ms烟馅。 -
distinct()
:確保只在過濾文本變化時才發(fā)送請求说庭。 -
transform(switchMap(...))
:為每個已經(jīng)通過debounce()
和distinct()
的搜索條目調(diào)用搜索服務(wù)。它取消并丟棄之前的搜索郑趁,僅返回最新的搜索服務(wù)流元素刊驴。 -
handleError()
:處理錯誤。這個簡單的例子只是把錯誤信息打印到控制臺;實(shí)際的應(yīng)用應(yīng)該做得更好寡润。
為儀表盤添加搜索組件
把英雄搜索的 HTML 元素添加到DashboardComponent
模版的底部捆憎。
//
lib/src/dashboard_component.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" [routerLink]="['HeroDetail', {id: hero.id.toString()}]" class="col-1-4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
<hero-search></hero-search>
最后,從hero_search_component.dart
中導(dǎo)入HeroSearchComponent
悦穿,并把它添加到directives
列表中攻礼。
// lib/src/dashboard_component.dart (search)
import 'hero_search_component.dart';
@Component(
selector: 'my-dashboard',
templateUrl: 'dashboard_component.html',
styleUrls: const ['dashboard_component.css'],
directives: const [CORE_DIRECTIVES, HeroSearchComponent, ROUTER_DIRECTIVES],
)
再次運(yùn)行應(yīng)用。在儀表盤中的搜索框中輸入一些文字栗柒。如果你輸入的字符匹配到了任何現(xiàn)有英雄名礁扮,你將會看到如下效果:
應(yīng)用的結(jié)構(gòu)與代碼
回顧本章示例的源代碼 在線示例 (查看源碼)知举。驗(yàn)證你是否有如下結(jié)構(gòu):
angular_tour_of_heroes/
|___lib/
| |___app_component.{css,dart}
| |___in_memory_data_service.dart (new)
| |___src/
| | |___dashboard_component.{css,dart,html}
| | |___hero.dart
| | |___hero_detail_component.{css,dart,html}
| | |___hero_search_component.{css,dart,html} (new)
| | |___hero_search_service.dart (new)
| | |___hero_service.dart
| | |___heroes_component.{css,dart,html}
|___test/
| |___app_test.dart
| |___...
|___web/
| |___main.dart
| |___index.html
| |___styles.css
|___pubspec.yaml
最后沖刺
旅程即將結(jié)束,不過你已經(jīng)收獲頗豐太伊。
- 添加了在應(yīng)用中使用 HTTP 時必要的依賴雇锡。
- 重構(gòu)了
HeroService
,從一個 Web API 來加載英雄僚焦。 - 擴(kuò)展了
HeroService
锰提,以支持post()
、put()
和delete()
方法芳悲。 - 更新了組件立肘,以允許英雄的添加、編輯和刪除名扛。
- 配置了一個內(nèi)存 Web API谅年。
- 學(xué)會了如何使用 Streams。
下一步
回到學(xué)習(xí)路徑肮韧,在哪里你可以閱讀更多關(guān)于在本教程中的概念和練習(xí)融蹂。