angular学习之路2

1,Http 方法返回单个值

所有的 HttpClient 方法都会返回某个值的 RxJS Observable

HTTP 是一个请求/响应式协议。你发起请求,它返回单个的响应。

通常,Observable 可以在一段时间内返回多个值。 但来自 HttpClient 的 Observable 总是发出一个值,然后结束,再也不会发出其它值。

具体到这次 HttpClient.get 调用,它返回一个 Observable<Hero[]>,顾名思义就是“一个英雄数组的可观察对象”。在实践中,它也只会返回一个英雄数组。

HttpClient.get 返回响应数据

HttpClient.get 默认情况下把响应体当做无类型的 JSON 对象进行返回。 如果指定了可选的模板类型 <Hero[]>,就会给返回你一个类型化的对象。

JSON 数据的具体形态是由服务器的数据 API 决定的

错误处理

凡事皆会出错,特别是当你从远端服务器获取数据的时候。 HeroService.getHeroes() 方法应该捕获错误,并做适当的处理。

要捕获错误,你就要使用 RxJS 的 catchError() 操作符来建立对 Observable 结果的处理管道(pipe)

从 rxjs/operators 中导入 catchError 符号,以及你稍后将会用到的其它操作符。

import { catchError, map, tap } from 'rxjs/operators';

窥探 Observable

HeroService 的方法将会窥探 Observable 的数据流,并通过 log() 函数往页面底部发送一条消息。

它们可以使用 RxJS 的 tap 操作符来实现,该操作符会查看 Observable 中的值,使用那些值做一些事情,并且把它们传出来。 这种 tap 回调不会改变这些值本身。

/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
  return this.http.get<Hero[]>(this.heroesUrl)
    .pipe(
      tap(_ => this.log('fetched heroes')),
      catchError(this.handleError<Hero[]>('getHeroes', []))
    );
}

修改英雄

添加如下的 save() 方法,它使用英雄服务中的 updateHero() 方法来保存对英雄名字的修改,然后导航回前一个视图。

save(): void {
   this.heroService.updateHero(this.hero)
     .subscribe(() => this.goBack());
 }

//保存到服务器

/** PUT: update the hero on the server */
updateHero (hero: Hero): Observable<any> {
  return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
    tap(_ => this.log(`updated hero id=${hero.id}`)),
    catchError(this.handleError<any>('updateHero'))
  );
}

往 HeroService 类中添加 addHero() 方法。

/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
  return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
    tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
    catchError(this.handleError<Hero>('addHero'))
  );
}

eroService.addHero() 和 updateHero 有两点不同。

  • 它调用 HttpClient.post() 而不是 put()

  • 它期待服务器为这个新的英雄生成一个 id,然后把它通过 Observable<Hero> 返回给调用者。

添加新英雄

add(name: string): void {
  name = name.trim();
  if (!name) { return; }
  this.heroService.addHero({ name } as Hero)
    .subscribe(hero => {
      this.heroes.push(hero);
    });
}

添加 HeroService.addHero()

往 HeroService 类中添加 addHero() 方法。:

/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
  return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
    tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
    catchError(this.handleError<Hero>('addHero'))
  );
}

删除某个英雄

delete(hero: Hero): void {
  this.heroes = this.heroes.filter(h => h !== hero);
  this.heroService.deleteHero(hero).subscribe();
}

虽然这个组件把删除英雄的逻辑委托给了 HeroService,但仍保留了更新它自己的英雄列表的职责。 组件的 delete() 方法会在 HeroService 对服务器的操作成功之前,先从列表中移除要删除的英雄

组件与 heroService.delete() 返回的 Observable 还完全没有关联。必须订阅它

如果你忘了调用 subscribe(),本服务将不会把这个删除请求发送给服务器。 作为一条通用的规则,Observable 在有人订阅之前什么都不会做

/** DELETE: delete the hero from the server */
deleteHero (hero: Hero | number): Observable<Hero> {
  const id = typeof hero === 'number' ? hero : hero.id;
  const url = `${this.heroesUrl}/${id}`;

  return this.http.delete<Hero>(url, httpOptions).pipe(
    tap(_ => this.log(`deleted hero id=${id}`)),
    catchError(this.handleError<Hero>('deleteHero'))
  );
}

注意

  • 它调用了 HttpClient.delete

  • URL 就是英雄的资源 URL 加上要删除的英雄的 id

  • 你不用像 put 和 post 中那样发送任何数据。

  • 你仍要发送 httpOptions

AsyncPipe

<li *ngFor="let hero of heroes$ | async" >

仔细看,你会发现 *ngFor 是在一个名叫 heroes$ 的列表上迭代,而不是 heroes

$ 是一个命名惯例,用来表明 heroes$ 是一个 Observable,而不是数组。

*ngFor 不能直接使用 Observable。 不过,它后面还有一个管道字符(|),后面紧跟着一个 async,它表示 Angular 的 AsyncPipe

AsyncPipe 会自动订阅到 Observable,这样你就不用再在组件类中订阅了。

import { Component, OnInit } from '@angular/core';

import { Observable, Subject } from 'rxjs';

import {
   debounceTime, distinctUntilChanged, switchMap
 } from 'rxjs/operators';

import { Hero } from '../hero';
import { HeroService } from '../hero.service';

@Component({
  selector: 'app-hero-search',
  templateUrl: './hero-search.component.html',
  styleUrls: [ './hero-search.component.css' ]
})
export class HeroSearchComponent implements OnInit {
  heroes$: Observable<Hero[]>;
  private searchTerms = new Subject<string>();

  constructor(private heroService: HeroService) {}

  // Push a search term into the observable stream.
  search(term: string): void {
    this.searchTerms.next(term);
  }

  ngOnInit(): void {
    this.heroes$ = this.searchTerms.pipe(
      // wait 300ms after each keystroke before considering the term
      debounceTime(300),

      // ignore new term if same as previous term
      distinctUntilChanged(),

      // switch to new search observable each time the term changes
      switchMap((term: string) => this.heroService.searchHeroes(term)),
    );
  }
}

RxJS Subject 类型的 searchTerms

searchTerms 属性声明成了 RxJS 的 Subject 类型。

Subject 既是可观察对象的数据源,本身也是 Observable。 你可以像订阅任何 Observable 一样订阅 Subject

你还可以通过调用它的 next(value) 方法往 Observable 中推送一些值,就像 search() 方法中一样。

search() 是通过对文本框的 keystroke 事件的事件绑定来调用的。

<input #searchBox id="search-box" (input)="search(searchBox.value)" />

串联 RxJS 操作符

如果每当用户击键后就直接调用 searchHeroes() 将导致创建海量的 HTTP 请求,浪费服务器资源并消耗大量网络流量。

应该怎么做呢?ngOnInit() 往 searchTerms 这个可观察对象的处理管道中加入了一系列 RxJS 操作符,用以缩减对 searchHeroes() 的调用次数,并最终返回一个可及时给出英雄搜索结果的可观察对象(每次都是 Hero[] )。

this.heroes$ = this.searchTerms.pipe(
  // wait 300ms after each keystroke before considering the term
  debounceTime(300),

  // ignore new term if same as previous term
  distinctUntilChanged(),

  // switch to new search observable each time the term changes
  switchMap((term: string) => this.heroService.searchHeroes(term)),
);

  • 在传出最终字符串之前,debounceTime(300) 将会等待,直到新增字符串的事件暂停了 300 毫秒。 你实际发起请求的间隔永远不会小于 300ms。

  • distinctUntilChanged() 会确保只在过滤条件变化时才发送请求。

  • switchMap() 会为每个从 debounce 和 distinctUntilChanged 中通过的搜索词调用搜索服务。 它会取消并丢弃以前的搜索可观察对象,只保留最近的。

借助 switchMap 操作符, 每个有效的击键事件都会触发一次 HttpClient.get() 方法调用。 即使在每个请求之间都有至少 300ms 的间隔,仍然可能会同时存在多个尚未返回的 HTTP 请求。

switchMap() 会记住原始的请求顺序,只会返回最近一次 HTTP 方法调用的结果。 以前的那些请求都会被取消和舍弃。

注意,取消前一个 searchHeroes() 可观察对象并不会中止尚未完成的 HTTP 请求。 那些不想要的结果只会在它们抵达应用代码之前被舍弃。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值