为了不再把相同的代码复制一遍又一遍,我们要创建一个单一的可复用的数据服务,并且把它注入到需要它的那些组件中。 使用单独的服务可以保持组件精简,使其集中精力为视图提供支持,并且,借助模拟(Mock)服务,可以更容易的对组件进行单元测试。
由于数据服务总是异步的,因此我们最终会提供一个基于承诺(Promise)的数据服务
一:创建英雄服务
客户向我们描绘了本应用更大的目标:想要在不同的页面中用多种方式显示英雄。 现在我们已经能从列表中选择一个英雄了,但这还不够。 很快,我们将添加一个仪表盘来显示表现最好的英雄,并创建一个独立视图来编辑英雄的详情。 所有这些视图都需要英雄数据。
目前,AppComponent
显示的是模拟数据。 不过,定义这些英雄并非组件的任务,否则我们没法与其它组件和视图共享这些英雄列表数据。 在这一章,我们将把获取英雄数据的任务重构为一个单独的服务,它将提供英雄数据,并把服务在所有需要英雄数据的组件间共享。
创建GeneralService
在./app目录下创建general.service.ts
注意:
我们遵循的文件命名约定是:服务名称的小写形式(基本名),加上.service
后缀。 如果服务名称包含多个单词,我们就把基本名部分写成中线形式 (dash-case)。 例如,SpecialSuperHeroService
服务应该被定义在special-super-hero.service.ts
文件中。
我们把这个类命名为GeneralService,并导出它,以供别人使用。
./app/general.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class GeneralService{
}
可注入的服务
注意,我们导入了 Angular 的Injectable
函数,并作为@Injectable()
装饰器使用这个函数。
注意:不要忘了写圆括号!如果忘了写,就会导致一个很难诊断的错误。
当 TypeScript 看到@Injectable()
装饰器时,就会记下本服务的元数据。 如果 Angular 需要往这个服务中注入其它依赖,就会使用这些元数据。
获取英雄数据
添加一个名叫getGenerals
的桩方法。
@Injectable()
export class GeneralService{
getGenerals():void{
};
}
重要:GeneralService
可以从任何地方获取Genera
数据 —— Web服务、本地存储或模拟数据源。 从组件中移除数据访问逻辑意味着你可以随时更改这些实现方式,而不影响需要这些英雄数据的组件
返回模拟的英雄数据
.app/general.service.ts
import { Injectable } from '@angular/core';
import {General} from '../bean/General';
import {Generals} from "../data/mock-general";
@Injectable()
export class GeneralService{
getGenerals():General[]{
return Generals;
};
}
导入GeneralService
.app/app.component.ts
import {GeneralService} from'./general.service';
注入 GeneralService
步骤
添加一个构造函数,并定义一个私有属性。
.app/app.component.ts
constructor(private generalService:GeneralService ){
}
构造函数自己什么也不用做,它在参数中定义了一个私有的generalService
属性,并把它标记为注入generalService
的靶点。
现在,当创建AppComponent
实例时,Angular 知道需要先提供一个generalService
的实例。
添加组件的
providers
元数据。
我们还得注册一个HeroService
提供商,来告诉注入器如何创建HeroService
。 要做到这一点,我们在@Component
组件的元数据底部添加providers
数组属性如下
.app/app.module.ts
import {GeneralService} from'./general.service';
providers: [GeneralService],
providers
数组告诉 Angular,当它创建新的AppComponent
组件时,也要创建一个GeneralService
的新实例。 AppComponent
会使用那个服务来获取英雄列表,在它组件树中的每一个子组件也同样如此。
AppComponent 中的 getHeroes()
该服务被存入了一个私有变量generalService
中。
我们可以在用一个方法调用此服务,并获得数据
getGenerals():void{
this.generals=this.generalService.getGenerals();
}
ngOnInit 生命周期钩子
毫无疑问,AppComponent
应该获取英雄数据并显示它。
你可能想在构造函数中调用getGenerals()
方法,但构造函数不应该包含复杂的逻辑,特别是那些需要从服务器获取数据的逻辑更是如此。构造函数是为了简单的初始化工作而设计的,例如把构造函数的参数赋值给属性。
只要我们实现了 Angular 的 ngOnInit 生命周期钩子,Angular 就会主动调用这个钩子。 Angular提供了一些接口,用来介入组件生命周期的几个关键时间点:刚创建时、每次变化时,以及最终被销毁时。
每个接口都有唯一的一个方法。只要组件实现了这个方法,Angular 就会在合适的时机调用它。
这是OnInit
接口的基本轮廓(但不要拷贝到你自己的代码中):
import { OnInit } from '@angular/core';export class AppComponent implements OnInit { ngOnInit(): void { }}
我们写了一个带有初始化逻辑的ngOnInit
方法,Angular会在适当的时候调用它。 在这个例子中,我们通过调用getGenerals()
来完成初始化。
ngOnInit(){
this.getGenerals();
}
我们的应用将会像期望的那样运行,显示英雄列表,并且在我们点击英雄的名字时,显示英雄的详情。
异步服务与承诺
我们的generalService
立即返回一个模拟的英雄列表,它的getGenerals()
函数签名是同步的。
问题:
但最终,英雄的数据会从远端服务器获取。当使用远端服务器时,用户不会等待服务器的响应。换句话说,你没法在等待期间阻塞浏览器界面。
为了协调视图与响应,我们可以使用承诺(Promise),它是一种异步技术,它会改变getGenerals()
方法的签名。
Promise具体介绍 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
GeneralService
会生成一个承诺
承诺 它就是一个承诺,在有了结果时,它承诺会回调我们。 我们请求一个异步服务去做点什么,并且给它一个回调函数。 它会去做(在某个地方),一旦完成,它就会调用我们的回调函数,并通过参数把工作结果或者错误信息传给我们。
把
GeneralService
的getGenerals
方法改写为返回承诺的形式:
基于承诺的行动
修改GeneralService
之后,this.generals
会被赋值为一个Promise
而不再是英雄数组。
getGenerals():void{
this.generals=this.generalService.getGenerals();
}
我们得修改这个实现,把它变成基于承诺的,并在承诺的事情被解决时再行动。 一旦承诺的事情被成功解决(Resolve),我们就会显示英雄数据。
我们把回调函数作为参数传给承诺对象的then方法:
getGenerals():void{
this.generalService.getGenerals()
.then(generals=>this.generals=generals);
}
在回调函数中,我们把服务返回的英雄数组赋值给组件的generals
属性。
我们的程序仍在运行,仍在显示英雄列表,在选择英雄时,仍然会把它/她显示在详情页面中。
.app/general.service.ts
import { Injectable } from '@angular/core';
import {General} from '../bean/General';
import {Generals} from "../data/mock-general";
@Injectable()
export class GeneralService{
getGenerals():Promise<General[]>{
return Promise.resolve(Generals);
};
}
.app/app.component.ts
import { Component,OnInit} from '@angular/core';
import {General} from "../bean/General";
import {GeneralService} from'./general.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'MY General';
// generals:General[]=Generals;
constructor(private generalService:GeneralService ){
}
selectGeneral:General;
generals:General[];
getGenerals():void{
this.generalService.getGenerals()
.then(generals=>this.generals=generals);
}
ngOnInit(){
this.getGenerals();
}
oSelect(item:General):void{
this.selectGeneral=item;
}
}
转载于:https://blog.51cto.com/12907581/1965618