第六章 真正的服务

点此处可以下载本章完成后的 示例代码 。运行方法为:

  1. 解压缩文件,进入heroes项目根目录。
  2. 使用npm install补上依赖包。也可以把之前备份的node_modules目录复制一份在项目根目录下。
  3. 使用json-server data.json启动后端服务。
  4. 使用npm start运行项目,打开浏览器访问http://localhost:4200/进入项目。

到目前为止,hero的数据还都是来源于一个内存中的数组,这显然与实际情况相去甚远。现在,我们使用一个json-server来实现真正的服务。json-server将数据存储在一个JSON格式的文件data.json中,并通过http服务提供一个RESTful API的web服务。

关于 RESTful API 和 JSON 的知识,请参见 附录C RESTful API

1. 使用json-server以提供RESTful服务

安装 json-server

我们假设你已经成功安装了json-server。如果没有,请参看 附录A Angular 环境搭建 来安装json-server。

建立data.json文件

data.json是我们的数据文件,json-server读取该文件并提供一个RESTful服务。这个文件可以放在任何位置,但是请 不要 放在src目录及其子目录之下,因为这个文件记录的数据会随时被更改,而Angular项目在运行时发现源代码文件被修改就会自动重加载。我们把它放在项目根目录上,与src目录平行。

{
  "heroes": [
    {
      "id": 1,
      "name": "The Monkey King",
      "desc": "I can fly!"
    },
    {
      "id": 2,
      "name": "The Spider Man",
      "desc": "I can climb!"
    }
  ]
}

启动json-server

在data.json所在文件夹,命令行输入

json-server data.json

json-server默认运行在本地3000端口,可以用-p指定不同的端口。

如果 json-server 正常运行,会输出下面的信息。

 \{^_^}/ hi!

  Loading data.json
  Done

  Resources
  http://localhost:3000/heroes

  Home
  http://localhost:3000

2. 英雄数据接口

  • GET /heroes:列出所有英雄
  • POST /heroes:新建一个英雄
  • GET /heroes/ID:获取某个指定英雄的信息
  • PUT /heroes/ID:更新某个指定英雄的信息(提供该英雄的全部信息)
  • PATCH /heroes/ID:更新某个指定英雄的信息(提供该英雄的部分信息)
  • DELETE /heroes/ID:删除某个英雄

3. 提供并注册HTTP服务

HttpModule并不是 Angular 的核心模块。 它是 Angular 用来进行 Web 访问的一种可选方式,并位于一个名叫 @angular/http 的独立附属模块中,并作为 Angular 的 npm 包之一而发布出来。

我们的应用将会依赖于 Angular 的http服务,它本身又依赖于其它支持类服务。 来自@angular/http库中的HttpModule保存着这些 HTTP 相关服务提供商的全集。

我们要能从本应用的任何地方访问这些服务,就要把HttpModule添加到AppModule的imports列表中。 这里同时也是我们引导应用及其根组件AppComponent的地方。

app.module.ts注册服务

import { HttpModule } from '@angular/http';
@NgModule({
  imports: [
    HttpModule,
  ],

4. 设置公用的API参数

我们可以把API的参数写到每一个service文件中,但是如果参数要修改的话(比如修改API服务器的地址和端口),就要一个文件一个文件修改,这样很麻烦,所以我们把参数写入了一个公用的文件:

src/app/service/api.service.ts

import { Headers } from '@angular/http';

export class ApiService {
    getUrl(): string {
        return 'http://localhost:3000';
    }
    getHeaders(): Headers {
        return new Headers({ 'Content-Type': 'application/json'});
    }
}

因为我们下面要使用 json-server 提供的服务,它默认的端口就是3000,而所有的数据交换格式将采用 JSON 格式。

同时,与HeroService一样,需要将ApiService服务添加到模块文件中。

app.module.ts

import { ApiService } from './service/api.service';
providers: [
  ApiService,
  ]

5. 修改 HeroService 中获得英雄信息的方法

HeroService可以从任何地方获取Hero数据 —— Web服务、本地存储或模拟数据源。 从组件中移除数据访问逻辑意味着你可以随时更改这些实现方式,而不影响需要这些英雄数据的组件。

修改getHeroes()方法

src/app/service/hero.service.ts

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import 'rxjs/add/operator/toPromise';

import { Hero } from '../domain/hero';

import { ApiService } from './api.service';

@Injectable()
export class HeroService {
    private api_url ;
    private headers ;

    constructor(private http: Http, private apiService: ApiService) {
        this.api_url = apiService.getUrl() + '/heroes';
        this.headers = apiService.getHeaders();
    }
    getHeroes(): Promise<Hero[]> {
        const url = `${this.api_url}`;
        return this.http.get(url, {headers: this.headers})
            .toPromise()
            .then(res => res.json() as Hero[])
            .catch(this.handleError);
    }
    private handleError(error: any): Promise<any> {
        console.error('An error occurred', error); 
        return Promise.reject(error.message || error);
    }

}

异步服务与承诺

return this.http.get(url, {headers: this.headers})
    .toPromise()
    .then(res => res.json() as Hero[])

这个请求发出后返回的是一个Observable(可观察对象),我们把它转换成Promise然后处理res(Http Response)。Promise提供异步的处理,注意到then中的写法,这个和我们传统编程写法不大一样,叫做lambda表达式,相当于是一个匿名函数,(input parameters) => expression,=>前面的是函数的参数,后面的是函数体。

在这里我们利用了toPromise操作符把Observable直接转换成Promise对象。

然而要想使用toPromise操作符,我们也必须先import

import 'rxjs/add/operator/toPromise';

在then回调中提取数据

.then(res => res.json() as Hero[])

错误处理

在getHeroes()的最后,我们catch了服务器的失败信息,并把它们传给了错误处理器:

.catch(this.handleError);

这是一个关键的步骤! 我们必须预料到 HTTP 请求会失败,因为有太多我们无法控制的原因可能导致它们频繁出现各种错误。

 private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }

6. 增加按照ID获得一个英雄的信息的方法

    getHeroById(id: number): Promise<Hero> {
        const url = `${this.api_url}/${id}`;
        return this.http.get(url, {headers: this.headers})
            .toPromise()
            .then(res => res.json() as Hero)
            .catch(this.handleError);
    }

7. 增加,删除,更新英雄操作

对照上文的查询操作,我们来完成另外这三个操作

  //新建Hero
    createHero(hero: Hero): Promise<Hero> {
      const url = `${this.api_url}`;
      return this.http
        .post(url, JSON.stringify(hero), {headers: this.headers})
        .toPromise()
        .then(res => res.json() as Hero)
        .catch(this.handleError);
    }

    //按name新建Hero
    createHeroByName(name: string): Promise<Hero> {
      let hero = {
        name: name
      }
      const url = `${this.api_url}`;
      return this.http
        .post(url, JSON.stringify(hero), {headers: this.headers})
        .toPromise()
        .then(res => res.json() as Hero)
        .catch(this.handleError);
    }

    //修改Hero
    updateHero(hero: Hero): Promise<Hero> {
      const url = `${this.api_url}/${hero.id}`;
      return this.http
        .put(url, JSON.stringify(hero), {headers: this.headers})
        .toPromise()
        .then(res => res.json() as Hero)
        .catch(this.handleError);
    }

    //删除某个Hero
    deleteHero(hero: Hero): Promise<void> {
      const url = `${this.api_url}/${hero.id}`;
      return this.http.delete(url, {headers: this.headers})
        .toPromise()
        .then(() => null)
        .catch(this.handleError);
    }

    //按id删除某个Hero
    deleteHeroById(id: number): Promise<void> {
      const url = `${this.api_url}/${id}`;
      return this.http.delete(url, {headers: this.headers})
        .toPromise()
        .then(() => null)
        .catch(this.handleError);
    }

至此,HeroService改造完毕。

results matching ""

    No results matching ""