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';
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值