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 {
return this.http
.get(`app/heroes/?name=${term}`)
.map((r: Response) => r.json().data as Hero[]);
}
}
三.HeroSearchComponent
<div id="search-component">
<h4>Hero Searchh4>
<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;
private searchTerms = new Subject();
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([]))
.catch(error => {
// TODO: real error handling
console.log(error);
return Observable.of([]);
});
}
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;
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([]))
.catch(error => {
// TODO: real error handling
console.log(error);
return Observable.of([]);
});
}
如果我们直接把每一次用户按键都直接传给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';