第六章 真正的服务
点此处可以下载本章完成后的 示例代码 。运行方法为:
- 解压缩文件,进入
heroes
项目根目录。- 使用
npm install
补上依赖包。也可以把之前备份的node_modules
目录复制一份在项目根目录下。- 使用
json-server data.json
启动后端服务。- 使用
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改造完毕。