发表于: 2017-05-14 23:07:28
1 923
今天完成的事情:
创建 HeroService
在app目录下创建一个名叫hero.service.ts的文件。
我们遵循的文件命名约定是:服务名称的小写形式(基本名),加上.service后缀。 如果服务名称包含多个单词,我们就把基本名部分写成中线形式 (dash-case)。 例如,SpecialSuperHeroService服务应该被定义在special-super-hero.service.ts文件中。
我们把这个类命名为HeroService,并导出它,以供别人使用。
src/app/hero.service.ts (starting point)
Copy Code
import { Injectable } from '@angular/core';
@Injectable()
export class HeroService {
}
可注入的服务
注意,我们导入了 Angular 的Injectable函数,并作为@Injectable()装饰器使用这个函数。
不要忘了写圆括号!如果忘了写,就会导致一个很难诊断的错误。
当 TypeScript 看到@Injectable()装饰器时,就会记下本服务的元数据。 如果 Angular 需要往这个服务中注入其它依赖,就会使用这些元数据。
虽然此时HeroService还没有任何依赖,但我们还是得加上这个装饰器。 作为一项最佳实践,无论是出于提高统一性还是减少变更的目的, 都应该从一开始就加上@Injectable()装饰器。
获取英雄数据
添加一个名叫getHeros的桩方法。
src/app/hero.service.ts (getHeroes stub)
Copy Code
@Injectable()
export class HeroService {
getHeroes(): void {} // stub
}
HeroService可以从任何地方获取Hero数据 —— Web服务、本地存储或模拟数据源。 从组件中移除数据访问逻辑意味着你可以随时更改这些实现方式,而不影响需要这些英雄数据的组件。
移动模拟的英雄数据
从app.component.ts文件中剪切HEROS数组,把它粘贴到app目录下一个名叫mock-heroes.ts的文件中。 还要复制import {Hero}...语句,因为我们的英雄数组用到了Hero类。
src/app/mock-heroes.ts
Copy Code
import { Hero } from './hero';
export const 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'}
];
我们导出了HEROES常量,以便可以在其它地方导入它 — 例如HeroService服务。
在刚刚剪切出HEROES数组的app.component.ts文件中,添加一个尚未初始化的heroes属性:
src/app/app.component.ts (heroes property)
Copy Code
heroes: Hero[];
返回模拟的英雄数据
回到HeroService,我们导入HEROES常量,并在getHeroes方法中返回它。 我们的HeroService服务现在是这样的:
src/app/hero.service.ts
Copy Code
import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
@Injectable()
export class HeroService {
getHeroes(): Hero[] {
return HEROES;
}
}
导入HeroService
我们可以在多个组件中使用 HeroService 服务了,先从 AppComponent 开始。
先导入HeroService,以便我们可以在代码中引用它。
src/app/app.component.ts (hero-service-import)
Copy Code
import { HeroService } from './hero.service';
不要new出HeroService
该如何在运行中获得一个具体的HeroService实例呢?
你可能想用new来创建HeroService的实例,就像这样:
Copy Code
heroService = new HeroService(); // don't do this
但这不是个好主意,有很多理由,例如:
我们的组件得弄清楚该如何创建HeroService。 如果有一天我们修改了HeroService的构造函数,我们不得不找出创建过此服务的每一处代码,并修改它。 围着补丁代码转圈很容易导致错误,还会增加测试负担。
我们每次使用new都会创建一个新的服务实例。 如果这个服务需要缓存英雄列表,并把这个缓存共享给别人呢?怎么办? 没办法,做不到。
我们把AppComponent锁定到HeroService的一个特定实现。 我们很难在不同的场景中切换实现。 例如,能离线操作吗?能在测试时使用不同的模拟版本吗?这可不容易。
注入 HeroService
你可以用两行代码代替用new时的一行:
添加一个构造函数,并定义一个私有属性。
添加组件的providers元数据。
添加构造函数:
src/app/app.component.ts (constructor)
Copy Code
constructor(private heroService: HeroService) { }
构造函数自己什么也不用做,它在参数中定义了一个私有的heroService属性,并把它标记为注入HeroService的靶点。
现在,当创建AppComponent实例时,Angular 知道需要先提供一个HeroService的实例。
更多依赖注入的信息,见依赖注入。
注入器还不知道该如何创建HeroService。 如果现在运行我们的代码,Angular 就会失败,并报错:
Copy Code
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
(异常:没有 HeroService 的提供商!(AppComponent -> HeroService))
我们还得注册一个HeroService提供商,来告诉注入器如何创建HeroService。 要做到这一点,我们在@Component组件的元数据底部添加providers数组属性如下:
src/app/app.component.ts (providers)
Copy Code
providers: [HeroService]
providers数组告诉 Angular,当它创建新的AppComponent组件时,也要创建一个HeroService的新实例。 AppComponent会使用那个服务来获取英雄列表,在它组件树中的每一个子组件也同样如此。
AppComponent 中的 getHeroes()
该服务被存入了一个私有变量heroService中。
我们可以在同一行内调用此服务,并获得数据。
Copy Code
this.heroes = this.heroService.getHeroes();
在真实的世界中,我们并不需要把一行代码包装成一个专门的方法,但无论如何,我们在演示代码中先这么写:
src/app/app.component.ts (getHeroes)
Copy Code
getHeroes(): void {
this.heroes = this.heroService.getHeroes();
}
ngOnInit 生命周期钩子
毫无疑问,AppComponent应该获取英雄数据并显示它。
你可能想在构造函数中调用getHeroes()方法,但构造函数不应该包含复杂的逻辑,特别是那些需要从服务器获取数据的逻辑更是如此。构造函数是为了简单的初始化工作而设计的,例如把构造函数的参数赋值给属性。
只要我们实现了 Angular 的 ngOnInit 生命周期钩子,Angular 就会主动调用这个钩子。 Angular提供了一些接口,用来介入组件生命周期的几个关键时间点:刚创建时、每次变化时,以及最终被销毁时。
每个接口都有唯一的一个方法。只要组件实现了这个方法,Angular 就会在合适的时机调用它。
更多生命周期钩子信息,见生命周期钩子。
这是OnInit接口的基本轮廓(但不要拷贝到你自己的代码中):
Copy Code
import { OnInit } from '@angular/core';
export class AppComponent implements OnInit {
ngOnInit(): void {
}
}
往export语句中添加OnInit接口的实现:
Copy Code
export class AppComponent implements OnInit {}
我们写了一个带有初始化逻辑的ngOnInit方法,Angular会在适当的时候调用它。 在这个例子中,我们通过调用getHeroes()来完成初始化。
app/app.component.ts (ng-on-init)
Copy Code
ngOnInit(): void {
this.getHeroes();
}
我们的应用将会像期望的那样运行,显示英雄列表,并且在我们点击英雄的名字时,显示英雄的详情。
明天计划的事情:
angular2
遇到的问题:
收获:
没有
评论