通过前面4部分,作出了一个能显示列表和显示详细信息的,并能修改数据的小程序。不过我们的数据目前是存在一个组件中的,当别的组件要使用数据的时候,要包含此组件,这明显是有问题的。
要有一个数据中心,这个数据中心专门负责提供数据,各个组件只是去获取数据来进行显示即可。这样,我们就需要服务。
1.将把获取数据的任务重构为一个单独的服务,它将提供数据,并把服务在所有需要数据的组件间共享。
2.在app目录下创建hero.service.ts文件,然后写入:
import { Injectable } from '@angular/core';
@Injectable()
export class HeroService {
}
注意上面的格式。要包含类,并使用类。这个修饰的是一个函数,要有()的,别忘记了。
当 TypeScript 看到@Injectable()装饰器时,就会记下本服务的元数据。 如果 Angular 需要往这个服务中注入其它依赖,就会使用这些元数据。(什么是注入其它依赖,后面再解释,目前知道这样写就是最标准的格式就行)
3.模拟数据源,新建文件mock-heroes.ts,然后把组件中的数据拷贝到此文件,作为数据源:
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数组export出去,不然会出错。
4.通过service返回模拟数据:
import { Injectable } from '@angular/core';
import { Hero } from './hero';//包含类
import { HEROES } from './mock-heroes';//包含数据源
@Injectable()
export class HeroService {
getHeroes(): Hero[] {
return HEROES;//返回数据
}
}
上面就将服务和数据源完成了。那么下面就讲如何使用这个service了。
5.在app.component.ts组件中使用service。
(1)导入service,就是包含文件import { HeroService } from './hero.service';
(2)在组件类中添加一个构造函数,并且定义一个私有属性。
constructor(private heroService: HeroService) { }
(3)注册一个HeroService提供商,来告诉注入器如何创建HeroService。 要做到这一点,我们在@Component组件的元数据底部添加providers数组属性.
providers: [HeroService]//在修饰器中添加此行
(4)在组件中,申明一个未初始化数组:
heroes: Hero[];
(5)在构造方法中给此数组赋值:
constructor(private heroService: HeroService) {
this.heroes = this.heroService.getHeroes();
}
这样操作后,就能正常显示了。效果和以前一样,只是做成了服务而已。
为何不在组件中使用new来申明服务呢?不想写了,反正这样不好,维护不方便等。
6.上面的问题是:在构造函数中调用getHeroes()方法,但构造函数不应该包含复杂的逻辑,特别是那些需要从服务器获取数据的逻辑更是如此。构造函数是为了简单的初始化工作而设计的,例如把构造函数的参数赋值给属性。
因此,只要我们实现了 Angular 的 ngOnInit 生命周期钩子,Angular 就会主动调用这个钩子。 Angular提供了一些接口,用来介入组件生命周期的几个关键时间点:刚创建时、每次变化时,以及最终被销毁时。
每个接口都有唯一的一个方法。只要组件实现了这个方法,Angular 就会在合适的时机调用它。
知道了吧,有这个潜规则。
因此,我们修改为:让angular来决定吧,什么时候初始化,不要放到构造方法中了。
(1)import { OnInit } from '@angular/core';
(2)
export class AppComponent implements OnInit {
constructor(private heroService: HeroService) {
}
title = 'hello world';
heroes: Hero[];
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
ngOnInit(): void {//带有初始化逻辑的ngOnInit方法,Angular会在适当的时候调用它。
this.heroes = this.heroService.getHeroes();
}
}
7.上面的代码,已经很不错了。但是,我们获取数据,是同步的。但是,从服务器获取数据,都是异步的,因此,还需要继续优化成异步的了。
我们的HeroService立即返回一个模拟的列表,它的getHeroes()函数签名是同步的。
但是数据会从远端服务器获取。当使用远端服务器时,用户不会等待服务器的响应。换句话说,你没法在等待期间阻塞浏览器界面。
为了协调视图与响应,我们可以使用承诺(Promise),它是一种异步技术,它会改变getHeroes()方法的签名。
8.修改成承诺模式:
(1)修改service的返回数据的接口:
getHeroes(): Promise<Hero[]> {
return Promise.resolve(HEROES);
}
(2)将使用service接口的地方修改成:
ngOnInit(): void {
//this.heroes = this.heroService.getHeroes();
this.heroService.getHeroes().then(heroes => this.heroes = heroes);//加了个then,这个就是回调了,可以达到异步的效果
}
上面就是完美的解决了数据,并使用了异步的服务。
承诺就是:你先调我,我保证有数据的时候告诉你,就是去调用你写的then后面的部分的方法
9.也可以测试下延迟情况:
(1)服务中添加:
getHeroesSlowly(): Promise<Hero[]> {
return new Promise(resolve => {
// Simulate server latency with 2 second delay
setTimeout(() => resolve(this.getHeroes()), 2000);
});
}
(2)组件中:
ngOnInit(): void {
//this.heroes = this.heroService.getHeroes();
this.heroService.getHeroesSlowly().then(heroes => this.heroes = heroes);
}
您会观察到,当你刷新网页的时候,要过一会儿数据才会显示出来,这个模拟了从服务器获取数据的情况。