HttpModule
- HttpModule是Angular用来进行web访问的一种可选的方式,它位于一个名叫@angular/http的独立附属模块中,并作为Angular的npm包之一而发布出来,所以它并不是angular的核心模块
mock服务(InMemoryWebApiModule)
- 导入InMemoryWebApiModule,并将其加入到模块的imports数组,ImMemoryWebApiModule将Http客服的默认的后端服务替换成了内存Web API服务
InMemoryWebApiModule.forRoot(InMemoryDataService)
forRoot()配置方法需要InMemoryDataService类实例,用来向内存数据库填充数据:往app目录下新增一个文件
in-memory-data.service.ts
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const heroes = [
{ id: 0, name: 'Zero' },
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
return {heroes};
}
}
Http请求
hero.service.ts
import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Hero } from './hero';
private heroesUrl = 'api/heroes'; // URL to web api
constructor(private http: Http) { }
getHeroes(): Promise {
return this.http.get(this.heroesUrl)
.toPromise()
.then(response => response.json() as Hero[])
.catch(this.handleError);
}
private handleError(error: any): Promise {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
解析
- Angular的http.get返回一个RxJS的Observable对象,Observable是一个管理异步数据流的强力方式
- 我们利用toPromise操作符把Observable直接转换成Promise对象
- Angular的Observable并没有一个toPromise操作符,没有打包在一起发布,Observable只是一个骨架实现,很多向toPromise这样的操作符,用于扩展Observable,为其添加有用的能力,如果我们需要这些能力,就要自己添加那些操作符,只要从RxJS库中导入他们就可以了,例如:
import 'rxjs/add/operator/toPromise';
- 在promise的then()回调中,我们调用HTTP的Response对象的json方法,以提取其中的数据
- 在getHeroes()的最后,我们catch了服务器的失败信息,并把它传给了错误处理器
- 我们要通过一个被拒绝(rejected)的承诺来把该错误用一个用户友好的格式返回给调用者,以便调用者把一个合适的错误信息显示给用户
Observable
- 请求并非总是“一次性”的,我们可以开始一个请求,并且取消它,在服务器对第一个请求做出回应之前,再开始另一个不同的请求,像这样一个 请求-取消-新请求 的序列用 承诺 是很难实现的,但对Observable来说则很简单
hero-search.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Hero } from './hero';
@Injectable()
export class HeroSearchService {
constructor(private http: Http) {}
search(term: string): Observable {
return this.http
.get(`api/heroes/?name=${term}`)
.map(response => response.json() as Hero[]);
}
}
解析:
- search()方法没有调用toPromise方法,而是从http.get方法中返回一个Observable对象,之后调用RxJS的map操作符 来从返回数据中提取heroes
hero-search.component.html
Hero Search
{{hero.name}}
解析:
- heroes属性现在是heroes列表的Observable对象,而不再只是heroes数组,ngFor不能用可观察对象做任何事,除非我们在它后面跟一个async pipe(AsyncPipe),这个async管道会订阅到这个可观察对象,并且为ngFor生成一个heroes列表
Subject
hero-search.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
// Observable class extensions
import 'rxjs/add/observable/of';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';
@Component({
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 300ms after each keystroke before considering the term
.distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => term // switch to new observable each time the term changes
// return the http search observable
? this.heroSearchService.search(term)
// or the observable of empty heroes if there was no search term
: Observable.of([]))
.catch(error => {
// TODO: add real error handling
console.log(error);
return Observable.of([]);
});
}
gotoDetail(hero: Hero): void {
let link = ['/detail', hero.id];
this.router.navigate(link);
}
}
解析:
- Subject是一个 可观察的 事件流中的生产者,searchTerms生成一个产生Observable,用作按名称搜索时的过滤条件
- 每当调用search()时都会调用next()来把新的字符串放进该主题的可观察流中
如果我们直接把每一次用户按键都直接传给HeroSearchService,就会发起一场HTTP请求风暴,这可不好,我们不希望占用服务器的资源,也不想耗光蜂窝移动的网络流量,可以在Observable字符串的后面串联一些Observable操作符,来归并这些请求,我们将对HeroSearchService发起更少的调用,并且仍然获得足够及时的反应
- 在传出最终字符串之前,debounceTime(300)将会等待,直到新增字符串的事件暂停300毫秒,我们实际发起请求的间隔永远不会小于300ms
- distinctUntilChanged确保只在过滤条件发生变化时才发送请求,这样就不会重复请求同一个搜索词
- switchMap()会为每一个从debounceTime和distinctUntilChanged中通过的搜索词调用search服务,它会取消并丢弃以前的搜索可观察对象,只保留最近的
- catch拦截失败的可观察对象