[Angular2] Http Client -(1)

现在主流浏览器都支持XHR和JSONP这两种基于HTTP的API,部分浏览器还支持Fetch。

Angular的HTTP客户端封装了XHR和JSONP API。

Http Client Demo

还是以Tour of Hero为例。这次我们从服务器获取数据,显示hero列表,能添加新的hero并保存到服务器。

AppComponent如下:

// app/app.component.ts

import {Component}         from 'angular2/core';
import {HTTP_PROVIDERS}    from 'angular2/http';

import {Hero}              from './hero';
import {HeroListComponent} from './hero-list.component';
import {HeroService}       from './hero.service';

@Component({
  selector: 'my-app',
  template: `
 <h1>Tour of Heroes</h1>
 <hero-list></hero-list>
 `,
  directives:[HeroListComponent],
  providers: [
    HTTP_PROVIDERS,
    HeroService,
  ]
})
export class AppComponent { }

HeroListComponent如下:

// app/hero-list.component.ts

import {Component, OnInit} from 'angular2/core';
import {Hero}              from './hero';
import {HeroService}       from './hero.service';

@Component({
  selector: 'hero-list',
  template: `
 <h3>Heroes:</h3>
 <ul>
 <li *ngFor="#hero of heroes">
 {{ hero.name }}
 </li>
 </ul>
 New Hero:
 <input #newHero />
 <button (click)="addHero(newHero.value); newHero.value=''">
 Add Hero
 </button>
 <div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
 `,
  styles: ['.error {color:red;}']
})
export class HeroListComponent implements OnInit

  constructor (private _heroService: HeroService) {}

  errorMessage: string;
  heroes:Hero[];

  ngOnInit() { this.getHeroes(); }

  getHeroes() {
    this._heroService.getHeroes()
                     .subscribe(
                       heroes => this.heroes = heroes,
                       error =>  this.errorMessage = <any>error);
  }

  addHero (name: string) {
    if (!name) {return;}
    this._heroService.addHero(name)
                     .subscribe(
                       hero  => this.heroes.push(hero),
                       error =>  this.errorMessage = <any>error);
  }
}

HeroListComponent组件模板使用NgFor指令来显示hero列表。在列表下面是一个文本框和Add按钮,我们输入新hero的名称,将它添加到数据库。我们使用本地模板变量newHero来在(click)事件绑定中访问文本框。当我们点击Add按钮,我们将该值传递给组件的addHero方法并清空它。

我们在构造函数中注入了HeroService。它是由父组件AppComponent提供的HeroService的实例。

HeroService改成使用HTPP Client,如下:

// app/hero.service.ts

import {Injectable}     from 'angular2/core';
import {Http, Response} from 'angular2/http';
import {Hero}           from './hero';
import {Observable}     from 'rxjs/Observable';

@Injectable()
export class HeroService {
  constructor (private http: Http) {}

  private _heroesUrl = 'app/heroes';  // URL to web api

  getHeroes () {
    return this.http.get(this._heroesUrl)
                    .map(res => <Hero[]> res.json().data)
                    .catch(this.handleError);
  }
  private handleError (error: Response) {
    // in a real world app, we may send the error to some remote logging infrastructure
    // instead of just logging it to the console
    console.error(error);
    return Observable.throw(error.json().error || 'Server error');
  }
}

这里我们将Http注入到HeroService的构造函数中。Http不是在Angular core中,而是在angular2/http库中,而且它在单独的脚本文件中。因此我们要在index.html添加:

<script src="node_modules/angular2/bundles/http.dev.js"></script>

接下来我们仔细看看http.get

return this.http.get(this._heroesUrl)
                .map(res => <Hero[]> res.json().data)
                .catch(this.handleError);

我们向get方法传入URL,它会访问服务器并返回数据。但是返回值貌似跟我们想得有点不一样哎,我们以为返回的会是promise,然后在使用的时候调用then()来获取数据。但是我们调用的是map()方法。显然他不是promise。

实际上,http.get方法返回的是一个HTTP响应的Observable,由RxJS库的提供的Observable 。 map是一个RxJS的操作符。

RxJS库

RxJS(Reactive Extensions)是个Angular提供的第三方库,实现异步观察模式(asynchronous observable pattern)。

在之前的例子中,我们安装了RxJS npm包,并在index.html中引用了RxJS脚本。这是使用HTTP client所需要的。

<script src="node_modules/rxjs/bundles/Rx.js"></script>

启用RxJS操作

RxJS非常大,通常只要我们所需要的特性就好了。Angular在rxjs/Observable模块中提供了简版的Observable,但是它缺乏我们所需要的所有的操作,包括上面提到的map方法。

我们在应用启动时引入所有RxJS操作:

// app/main.ts (import rxjs)

// Add all operators to Observable
import 'rxjs/Rx';

看一看我们为什么需要map()方法。

return this.http.get(this._heroesUrl)
                .map(res => <Hero[]> res.json().data)
                .catch(this.handleError);

这个response对象并不包含我们可以直接使用的数据。我们需要调用response.json()将服务器返回的字节转换成JSON对象。这是因为Angular的HTTP Client使用的规范是ES2015中Fetch方法返回的response对象。规范中定义了一个json()方法将response正文转换成JSON对象。json()返回的也不是数组,而是将JSON结果包装在一个对象中,这个对象有个属性data。这是web api的惯例,基于安全考虑。

不要返回response对象

getHeroes()可以直接返回Observable<Response>。但是这并不是个好主意。数据service的目的就是向用户隐藏与服务器交互的细节。组件调用HeroService需要的只是数据,而不是response对象。

错误处理

记住始终进行错误处理。使用Observable的catch操作处理错误,根据需要决定是否将错误抛到组件中。

在HeroListComponent中订阅

回到HeroListComponent,我们要调用heroService.get,为subscribe提供第二个函数用于错误处理:

getHeroes() {
  this._heroService.getHeroes()
                   .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}

在console查看结果

在开发的时候我们通常将结果输出到console中。可以使用Observable的do操作,将它放在mapcatch之间:

return this.http.get(this._heroesUrl)
                .map(res => <Hero[]> res.json().data)
                .do(data => console.log(data)) // eyeball results in the console
                .catch(this.handleError);

向服务器发送数据

HeroListComponent中添加一个addHero方法:

addHero (name: string) : Observable<Hero>

我们的数据服务遵循REST风格,使用POST请求,数据放在请求的body中,就像这样:

{ "name": "Windstorm" }

服务器会生成id,并返回完整的JSON对象。

HeroService中实现addHero

import {Headers, RequestOptions} from 'angular2/http';
addHero (name: string) : Observable<Hero>  {

  let body = JSON.stringify({ name });
  let headers = new Headers({ 'Content-Type': 'application/json' });
  let options = new RequestOptions({ headers: headers });

  return this.http.post(this._heroesUrl, body, options)
                  .map(res =>  <Hero> res.json().data)
                  .catch(this.handleError)
}

注意,post方法的第二个参数是JSON字符串,第三个参数是Content-Type

将Response还原成Promise

尽管http API返回的是Observable<Response>,但是我们可以把它转换成Promise。

// app/toh/hero.service.ts (promise-based)

getHeroes () {
  return this.http.get(this._heroesUrl)
                  .toPromise()
                  .then(res => <Hero[]> res.json().data, this.handleError)
                  .then(data => { console.log(data); return data; }); // eyeball results in the console
}
addHero (name: string) : Promise<Hero> {
  let body = JSON.stringify({ name });
  let headers = new Headers({ 'Content-Type': 'application/json' });
  let options = new RequestOptions({ headers: headers });
  return this.http.post(this._heroesUrl, body, options)
             .toPromise()
             .then(res => <Hero> res.json().data)
             .catch(this.handleError);
}
private handleError (error: any) {
  // in a real world app, we may send the error to some remote logging infrastructure
  // instead of just logging it to the console
  console.error(error);
  return Promise.reject(error.message || error.json().error || 'Server error');
}
// app/toh/hero.service.ts (observable-based)

getHeroes () {
  return this.http.get(this._heroesUrl)
                  .map(res => <Hero[]> res.json().data)
                  .do(data => console.log(data)) // eyeball results in the console
                  .catch(this.handleError);
}
addHero (name: string) : Observable<Hero>  {
  let body = JSON.stringify({ name });
  let headers = new Headers({ 'Content-Type': 'application/json' });
  let options = new RequestOptions({ headers: headers });
  return this.http.post(this._heroesUrl, body, options)
                  .map(res =>  <Hero> res.json().data)
                  .catch(this.handleError)
}
private handleError (error: Response) {
  // in a real world app, we may send the error to some remote logging infrastructure
  // instead of just logging it to the console
  console.error(error);
  return Observable.throw(error.json().error || 'Server error');
}

将observable转换成promise只需要调用toPromise(success, fail)。将observable的map回调函数移到第一个success参数,catch回调函数移到第二个fail参数就行了。也可以使用addHero方法中的then.catch模式。

HeroListComponent中调用:

// app/hero-list.component.ts (promise-based)

getHeroes() {
  this._heroService.getHeroes()
                   .then(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}
addHero (name: string) {
  if (!name) {return;}
  this._heroService.addHero(name)
                   .then(
                     hero  => this.heroes.push(hero),
                     error =>  this.errorMessage = <any>error);
}
// app/hero-list.component.ts (observable-based)

getHeroes() {
  this._heroService.getHeroes()
                   .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}
addHero (name: string) {
  if (!name) {return;}
  this._heroService.addHero(name)
                   .subscribe(
                     hero  => this.heroes.push(hero),
                     error =>  this.errorMessage = <any>error);
}

参考资料

Angular2官方文档

你可能感兴趣的:([Angular2] Http Client -(1))