Observable(可觀察物件)
Http服務中的每個方法都返回一個Http Response物件的Observable例項.我們通常會把這個Observable例項轉換成promise,並把這個promise返回給呼叫者,這一節我們將學會直接返回Observable,並且討論何時以及為何那樣做會更好.
一.背景
一個Observable是一個事件流, 我們可以用陣列操作符來處理他.
Angular核心中提供了對可觀察物件的基本支援,而我們可以自己從RxJS物件中引入操作符和擴充套件.
我們一般會在一個Observable後串聯一個toPromise,該操作符把Observable轉換成promise,並且把promise返回給呼叫者.
轉換成promise通常是更好的選擇,我們通常要求http.get獲取單塊資料,只要接收到資料就算完成,使用promise這種形式的結果是讓呼叫更容易寫.
但是請求並非總是一次性的,我們可以開始一個請求,並且取消他,在伺服器對第一個請求做出迴應之前,再開始另一個不同的請求,這種請求-取消-新請求對promise很難實現,但是對Observable卻很容易.
二.按名搜尋
我們要在使用者在搜尋框輸入一個名字時,我們將不斷髮起http請求,以獲得按名字過濾的英雄.
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs';
import { Hero } from './hero';
@Injectable()
export class HeroSearchService {
constructor(private http : Http) {}
search(term: string): Observable<Hero[]> {
return this.http
.get(`app/heroes/?name=${term}`)
.map((r: Response) => r.json().data as Hero[]);
}
}
三.HeroSearchComponent
<div id="search-component">
<h4>Hero Search</h4>
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<div>
<div *ngFor="let hero of heroes | async"
(click)="gotoDetail(hero)" class="search-result" >
{{hero.name}}
</div>
</div>
</div>
由於heroes現在是英雄列表的Observable物件,而不是英雄陣列, *ngFor不能用Observable做任何事,除非在他後面加一個async pipe.這個async會訂閱到這個Observable,並且為ngFor生成一個英雄陣列.
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';
@Component({
moduleId: module.id,
selector: 'hero-search',
templateUrl: 'hero-search.component.html',
styleUrls: [ 'hero-search.component.css' ],
providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
heroes: Observable<Hero[]>;
private searchTerms = new Subject<string>();
constructor(
private heroSearchService: HeroSearchService,
private router: Router) {}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
ngOnInit(): void {
this.heroes = this.searchTerms
.debounceTime(300) // wait for 300ms pause in events
.distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => term // switch to new observable each time
// return the http search observable
? this.heroSearchService.search(term)
// or the observable of empty heroes if no search term
: Observable.of<Hero[]>([]))
.catch(error => {
// TODO: real error handling
console.log(error);
return Observable.of<Hero[]>([]);
});
}
gotoDetail(hero: Hero): void {
let link = ['/detail', hero.id];
this.router.navigate(link);
}
}
四.搜尋詞
仔細看這個searchTerms
private searchTerms = new Subject<string>();
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
subject是一個可觀察的事件流中的生產者,searchTerms生成一個產生字串的Observable,用作按名稱搜尋時的過濾條件.
每當呼叫search時都會呼叫next來把新的字串放進該主題的可觀察流中.
五.初始化Heroes屬性
subject也是一個Observable物件,我們要把搜尋詞的流轉換成Hero陣列的流,並把結果賦值給heroes屬性.
heroes: Observable<Hero[]>;
ngOnInit(): void {
this.heroes = this.searchTerms
.debounceTime(300) // wait for 300ms pause in events
.distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => term // switch to new observable each time
// return the http search observable
? this.heroSearchService.search(term)
// or the observable of empty heroes if no search term
: Observable.of<Hero[]>([]))
.catch(error => {
// TODO: real error handling
console.log(error);
return Observable.of<Hero[]>([]);
});
}
如果我們直接把每一次使用者按鍵都直接傳給HeroSearchService,就會發起一場HTTP請求風暴,但是我們可以在字串Observable後面串聯一些Observable操作符,來歸併這些請求.我們將對HeroSearchService發起更少的呼叫,並且仍然獲得足夠的相應,做法如下:
(1)在傳出最終字串之前, debounce(300)將會等待,直到新增字串的事件暫停了300毫秒,
(2)distinctUntilChange確保只用過濾條件發生變化時才會傳送請求
(3)switchMap會為每個從debounce和distinctUntilChage中通過的搜尋詞調用搜索服務,他會取消並丟棄以前的搜尋可觀察物件,並且只保留最近的.
switchMap操作符是非常智慧的.
每次符合條件的按鍵事件都會觸發一次對http方法的呼叫.即使在傳送每個請求都有300毫秒的延遲,我們仍然可能同時擁有在途的http請求,並且只返回最近一次http呼叫返回的可觀察物件,因為以前的呼叫都被取消或者丟棄了.
如果搜尋框為空,我們還可以短路掉這次http方法呼叫,並且直接返回一個包含空陣列的可觀察物件.
注意: 取消HeroSearchService的可觀察物件並不會終止一個未完成的http請求,除非服務支援這個特性.
匯入RxJS操作符
Angular的基本版Observable實現中, RxJS操作符是不可用的,我們必須匯入他們以擴充套件Observable.
在本檔案頂部寫上import語句就可以為Observable擴展出這裡用到的操作符.
但也可以用另外的方法
// Observable class extensions
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
然後在頂級的AppModule中一次性匯入該檔案
import './rxjs-extensions';