1. 程式人生 > >Observable(可觀察物件)

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