第五章 服务

点此处可以下载本章完成后的 示例代码 。运行方法为:

  1. 解压缩文件,进入heroes项目根目录。
  2. 使用npm install补上依赖包。也可以把之前备份的node_modules目录复制一份在项目根目录下。
  3. 使用npm start运行项目,打开浏览器访问http://localhost:4200/进入项目。

下面我们讨论一下如何通过“数据服务”来访问数据。

数据服务,负责提供一个可复用的组件,并且把自己注入到需要它的组件中。由于数据服务通常要访问文件系统、数据库系统或者基于网络的服务提供者,所以数据服务总是异步的,这就需要提供一个基于承诺(Promise)的数据服务。

1. 首先建一个英雄服务

在app目录下建service文件夹,命令行输入简单的指令,完成hero.service.ts的建立。

ng g s service/hero
installing service
  create src/app/service/hero.service.spec.ts
  create src/app/service/hero.service.ts
  WARNING Service is generated but not provided, it must be provided to be used

Angular CLI 会自动创建一个src/app/service目录,并且把hero.service.ts放在这个目录下。将来再有新的服务(service)时,也集中放在这个目录下。这样做的目的,是因为serive层是相对独立的一层,位于其他前端代码与后端服务之间,最终是要负责与后端服务通信的,集中存放便于管理。

我们遵循的文件命名约定是:服务名称的小写形式(基本名),加上.service后缀。 如果服务名称包含多个单词,我们就把基本名部分写成中线形式 (dash-case)。 例如,SpecialSuperHeroService服务应该被定义在special-super-hero.service.ts文件中。

刚刚创建好的文件,内容是这样的:


import { Injectable } from '@angular/core';

@Injectable()
export class HeroService {

  constructor() { }

}

可注入服务

注意,我们导入了 Angular 的Injectable函数,并作为@Injectable()装饰器使用这个函数。注意不要落下圆括号。

当 TypeScript 看到@Injectable()装饰器时,就会记下本服务的元数据。 如果 Angular 需要往这个服务中注入其它依赖,就会使用这些元数据。

虽然此时 HeroService 还没有任何依赖,但我们还是得加上这个装饰器。作为一项最佳实践,无论是出于提高统一性还是减少变更的目的, 都应该从一开始就加上@Injectable()装饰器。

2. 查询获取英雄数据

HeroService中添加一个getHeroes()的方法:

src/app/service/hero.service.ts


import { Injectable } from '@angular/core';
import { HEROES } from '../mock-heroes';
import { Hero } from '../domain/hero';

@Injectable()
export class HeroService {

  constructor() { }
  getHeroes(): Promise<Hero[]> {
    return Promise.resolve(HEROES);
  }
}

HeroService可以从任何地方获取Hero数据 —— Web服务、本地存储或模拟数据源。 从组件中移除数据访问逻辑意味着你可以随时更改这些实现方式,而不影响需要这些英雄数据的组件。

目前英雄的数据从本地数组中获取,但是终究会从远端服务器获取。当使用远端服务器时,用户不会等待服务器的响应。换句话说,你没法在等待期间阻塞浏览器界面。为了协调视图与响应,我们可以使用承诺(Promise),它是一种异步技术,它需要getHeroes()方法的签名为Promise。

HeroService会生成一个承诺,在有了结果时,它承诺会回调我们。 我们请求一个异步服务去做点什么,并且给它一个回调函数。 它会去做(在某个地方),一旦完成,它就会调用我们的回调函数,并通过参数把工作结果或者错误信息传给我们。

3. 导入并注入HeroService

向需要注入HeroService的component.ts文件中导入服务。下面以 heroes/hero-list/hero-list.component.ts 为例介绍,其他需要英雄数据的组件都参照修改。注意目录的相对关系。

修改组件

hero-list.component.ts

import { Component, OnInit } from '@angular/core';
import { Hero } from '../domain/hero';
import { HeroService } from '../../service/hero.service';
//import { HEROES } from '../../mock-heroes'; <== 删掉此行

@Component({
  selector: 'app-hero-list',
  templateUrl: './hero.component.html',
  styleUrls: ['./hero.component.css']
})

export class HeroListComponent implements OnInit {


  constructor(private heroService: HeroService) { } //增加了heroService

  heroes : Hero[]; //原来是:heroes = HEROES;
  selectedHero : Hero;

  ngOnInit() {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

}

构造函数自己什么也不用做,它在参数中定义了一个私有的heroService属性,并把它标记为注入HeroService的靶点。现在,当创建AppComponent实例时,Angular 知道需要先提供一个HeroService的实例。

ngOnInit 生命周期钩子。只要我们实现了 Angular 的 ngOnInit 生命周期钩子,Angular 就会主动调用这个钩子。 Angular提供了一些接口,用来介入组件生命周期的几个关键时间点:刚创建时、每次变化时,以及最终被销毁时。每个接口都有唯一的一个方法。只要组件实现了这个方法,Angular 就会在合适的时机调用它。

添加注入:添加提供商

我们还得注册一个HeroService提供商,来告诉注入器如何创建HeroService。 要做到这一点,我们可以在@Component组件的元数据底部添加providers数组属性,但是更好的处理方法是在app.module.ts中添加。

app.module.ts

import { HeroService } from './service/hero.service';
providers: [HeroService]

providers数组告诉 Angular,当它创建新的AppComponent组件时,也要创建一个HeroService的新实例。 AppComponent会使用那个服务来获取英雄列表,在它组件树中的每一个子组件也同样如此。

察看应用

打开浏览器,可以看到英雄列表又出现了。

results matching ""

    No results matching ""