Services
英雄之旅功能在不断增加,且我们预期在不久的将来会对它添加更多的组件。
多个组件都将会需要访问英雄数据,但是我们不希望到处赋值粘贴同样的到处。相反的,我们将会创建一个单独的可重用数据服务,并学习如何注入到需要的组件中。
重构数据访问到一个分离的service,可以保持组件简洁并专注于提供视图。同时它使组件的unit test更加简单,只需利用一个mock service。
由于数据服务总是异步的,我们将用基于Promise版本的数据服务来完成这章内容。
运行在线示例来演示这部份内容。
从哪里开始
在我们继续英雄之旅之前,让我们先验证以下的文件结构。如果不对,我们需要返回上一章重新学习。
angular2-tour-of-heroes
├── app
│ ├── app.component.ts
│ ├── hero.ts
│ ├── hero-detial.component.ts
│ └── main.ts
├── node_modules ...
├── typings ...
├── index.html
├── package.json
├── styles.css
├── systemjs.config.js
├── tsconfig.json
└── typings.json
保持app运行
我们想要运行TypeScript编译器,让它监控文件变更,并且运行服务器。为此我们将执行命令:
npm start
这个命令将运行监控模式的编译器,启动服务器,在浏览器中打开app,并保证app在我们不断创建英雄之旅时运行。
创建一个Hero Service
我们的利益相关者为app分享了他们更大的愿景。他们告诉我们想要在不同的页面通过多种方式展示英雄。我们现在已经可以在英雄列表中选择一个英雄。不久我们将在顶部添加一个控制面面板展示英雄并创建一个单独的页面编辑英雄详情。这三个页面都需要英雄数据。
此时AppComponent
中定义了模拟的英雄数据用于展示。我们有至少两个反对意见。第一,定义英雄不是组件要做的工作。第二,我们无法简单得与其他组件和视图共享英雄列表。
我们可以重构英雄数据到一个单独的service以获得提供英雄的业务能力,并且与所有需求英雄的组件共享。
创建HeroService
在app
文件夹中创建一个名为hero.service.ts
的文件。
我们命名这个类为HeroService
并且将之export以便其他文件引入。
import { Injectable } from '@angular/core';
@Injectable()
export class HeroService {
}
注入service
注意到我们引入了Angular的Injectable
方法并且将之作为一个@injectable()
装饰器。
不要忘记括号!忽略他们将会导致很难诊断的错误。
TypeScript看到@Injectable()
装饰器并且发出关于我们的service的元数据,Angular需要这个元数据来注入依赖到service中。
HeroService
此时并没有任何依赖,但我们仍然添加了这个装饰器。从一开始就应用@Injectable()
装饰器来保证一致性和未来适用性是一个“最佳实践”。
获取英雄
添加getHeroes方法存根。
@Injectable()
export class HeroService {
getHeroes() {
}
}
我们先暂停一下来强调一个重点。
我们service的消费者并不知道它是如何获取数据的。HeroService
可能从任何地方获取Hero
数据,可能是一个web service或者一个本地存储或者是一个模拟数据源。
这就是为什么把数据访问从组件中移除会更漂亮。我们可以随时改变实现的思路,无论什么原因,而不需要碰组件的代码。
模拟英雄数据
我们已经在AppComponent
中已经有模拟的英雄数据,但它并不属于这里,我们将把它移动到单独的文件中。
从app.component.ts
中剪切HEROES
数组并且复制到app
文件夹下的新文件mock-heroes.ts
中。同时复制improt {Hero} ...
语句,因为heroes数组使用了Hero
类。
import { Hero } from './hero';
export var HEROES: Hero[] = [
{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'}
];
我们exportHEROES
常量以便以我们可以在任何地方引入它,比如HeroService
。
同时,回到app.component.ts
中剪切HEROES
数组的地方,我们留下一个未初始化的heroes
属性。
heroes: Hero[];
返回模拟英雄数据
回到HeroService
,我们引入模拟的HEROES
并且在getHeroes
方法中将其返回。现在HeroService
看起来是这样的:
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
@Injectable()
export class HeroService {
getHeroes() {
return HEROES;
}
}