网站首页 » 前端开发 » Angular 2 » Angular 4.x 服务示例(Services)
上一篇:
下一篇:

Angular 4.x 服务示例(Services)

随着应用的不断完善,更多的组件需要用到 hero 数据。

你无需一遍又一遍地复制、拷贝代码,因为在这遍文章里我们要通过服务来提供数据,创建一个数据服务,然后在需要英雄数据的组件中注入这个数据服务。

通过把数据独立出来做成一个服务,可以让你的组件更轻、更爽、更专注。还可以更容易的去做单元测试。

因为数据服务总是异步,所以你需要通过 Promise-based version 的数据服务来完成数据交互。

准备

在我们开始英雄之旅之前,保证你的文件结果如下,如果不是,请回头看看之前的教程。

Angular 2 服务示例(Services)

启动服务

ng serve --open

上面的命令行就是启动服务并运行 应用。这个命令行有几个作用:1.启动应用服务;2.监听文件变化。3.重建APP。

创建英雄服务

要想在不同的页面中显示多种形式的数据,比如:英雄列表,英雄详情,及编辑英雄。这些都需要英雄数据作为支持。

目前我们是在 AppComponent 中定义 hero 数据的,然而定义数据并不是组件的职责,并且以这样的方式创建的数据你也很难分享给其它组件作用。本教程中我们将会把这些数据抽出来做成一个服务,并且数据是可以被分享的(只要组件有需要,都可以调用这个服务)。

创建 HeroService

在 app 目录下创建一个名为 hero.service.ts 的文件。

提示

服务文件的命名约定:小写字母后面跟上.service。对于多个单词我们可以用破折号隔开每个单词都是小写。

比如:SpecialSuperHeroService 服务的文件名为 special-super-hero.service.ts

定义一个 HeroService 类并暴露它,以及被其它组件 import

src/app/hero.service.ts (starting point)
import { Injectable } from '@angular/core';

@Injectable()
export class HeroService {
}

注入服务

注意:保证你已经引入了 Angular 的 Injectable 函数并把这个函数作为了一个装饰器。

注意:

别忘记写括号(@Injectable()),漏了它们会导致错误,并且错误不容易定位。

@Injectable() 会告诉 TypeScript 请求源数据服务。源数据指定了Angular 可能需要依赖这个服务。

请求 hero 数据

添加 getHeroes 方法

src/app/hero.service.ts (getHeroes stub)
@Injectable()
export class HeroService {
  getHeroes(): void {} // stub
}

HeroService 可以在任意的地方调用 Hero 数据,本地存储或者模拟数据。把数据单独出来,你就可以更好的实现组件,而不会影响到 hero 数据。

独立数据

在 app 目录下新建一个名为 mock-heroes.ts 的文件,把 app.component.ts 里面的 heroes 数据剪切出来放,粘到 mock-heroes.ts 里。另外再复制一份 import {Hero} … 声明,因为这个heroes 数组需要用到这个 Hero 类。

src/app/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 常量能在其它地方像 HeroService 一样可以用import 引入,我们需要把这个 HEROES 常量暴露出去。

在 app.component.ts  添加一个未初始化的 heroes 属性。

src/app/app.component.ts (heroes property)
heroes: Hero[];

返回 heroes 数据

回到 HeroService 中,import 一下模拟英雄数据,并且通过 getHeroes() 把数据返回来。HeroService 文件代码如下:

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

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
  getHeroes(): Hero[] {
    return HEROES;
  }
}

import 英雄服务

要想在组件中使用 HeroService  服务。你需先在 AppComponent 中把这个服务 import 进来。

src/app/app.component.ts (hero-service-import)
import { HeroService } from './hero.service';

不要用 new

AppComponent 要怎么样获取 heroService 实例呢?

你可以像下面这样用 new 关键字来创建一个新的实例:

src/app/app.component.ts
heroService = new HeroService(); // don't do this

然而,这并不是最明智的选择,理由如下:

1、组件需要知道怎么去创建 HeroService 。如果你更改了 HeroService 构造器,你必需找到并更新所有用到这个 service 服务地方。多个地方修改代码更容易引起错误,并且会增加测试负担。

2、第次使用你都得 new 一个实例。要是要缓存数据并想分享给其它组件使用怎么办?做不到。

3、当 AppComponent 锁定了一些 HeroService 特定实现后,对于不同场景的切换实现(比如:脱机运行或者使用不同版本测试),将会变得很困难。

注入 HeroService

不用 new 方式来实现,取而代之的是添加两行代码。

1、添加一个构造器(constructor)并且定义一个属性。

2、把它添加到组件的 providers 元数据中。

添加构造器:

src/app/app.component.ts (constructor)
constructor(private heroService: HeroService) { }

在这里构造器本身并没有做什么事,同时这个参数定义了一个私有属性:heroService 作为 HeroService  的注入点。

注入器还不知道怎么去创建一个 HeroService,如果你现在运行代码,Angular 将会报错:

报错

EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)

教授注入器如何去创建一个 HeroService 在组件的元数据的下方添加 providers  数组属性。

src/app/app.component.ts (providers)
providers: [HeroService]

当 创建 AppComponent 的时候, providers 数组会告诉Angular 去创建一个新的 HeroService  实例,同时它的子组件也可以使用这些 hero 数据。

在 AppComponent 中使用 getHeroes()

服务已经保存到了一个私有属性 heroService 中。

通过一行代码你就可以获取 hero 数据。

src/app/app.component.ts
this.heroes = this.heroService.getHeroes();

你也没必需用方法专门把一行代码包裹起来,怎么写随你!

getHeroes(): void {
  this.heroes = this.heroService.getHeroes();
}

ngOnInit 生命周期

AppComponent 需要准备无误地获取和显示 hero 数据。

你可能想在构造器中调用 getHeroes() 方法,但一个构造器不应该包含复杂的逻辑,特别是一个用于服务的构造器,比如:获取数据的方法。构造器用于一些简单的初始化。

要想让 Angular 去调用 getHeroes() 方法,你可以通过实现 Angular 的 ngOnInit 生命周期勾子来调用,Angular 会提供一个在组件生命周期监控不同时刻的接口。创建时,每次变化后,以及最后的销毁时刻。

每一个接口都有一个方法,当组件实现了这个方法后,Angular 会在合适的时刻调用它。

下面就是 OnInit  接口基本的结构(不要把下面的代码拷贝到你的文件中)

src/app/app.component.ts

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

export class AppComponent implements OnInit {
  ngOnInit(): void {
  }
}

给你要暴露的声明添加 OnInit 接口:

export class AppComponent implements OnInit {}

在 ngOnInit 方法里初始化逻辑,Angular 会在合适的时刻调用它。

src/app/app.component.ts (ng-on-init)
ngOnInit(): void {
  this.getHeroes();
}

此时你就会如期地看到,当初的显示效果,一个英雄列表,当点击其中一个英雄后就显示对应的详情。

异步服务和 Promises

HeroService 会立即返回一个 heroes 的模拟数据。getHeroes() 是同步的。

src/app/app.component.ts
this.heroes = this.heroService.getHeroes();

最终,远端服务将会返回 hero 数据,当使用无端服务时,用户无需等待服务器响应,也不会阻塞 UI 的渲染。

要想让渲染与数据同步显示你可以使用 Promises ,它是异步的技术。

Promise 实现

Promise 本质上就是等待结果就绪后执行回调。请求一个异步的服务来实现一些需求,然后给一个回调函数。服务器就会返回结果或者错误信息。

用 Promise 返回数据方式更新 HeroService 的 getHeroes() 方法。

src/app/hero.service.ts (excerpt)
getHeroes(): Promise<Hero[]> {
  return Promise.resolve(HEROES);
}

你仍然需要模仿数据,你正在模拟一个快速,零延迟服务 器行为,以 Promise 的形式返回英雄数据。

使用 Promise

改完 HeroService 后,this.heroes 被设置成了 Promise ,而不是一个数组

src/app/app.component.ts (getHeroes – old)
getHeroes(): void {
  this.heroes = this.heroService.getHeroes();
}

你需要修改实现,来对Promise 返回的数据进行处理,当Promise 获取资源成功后,heroes 将会显示出来。

把函数当作一个参数传递给 then() 方法:

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

回调函数会把组件的 heroes 属性保存到 heroes 数组中。

现在应用又起死回生了。

回顾结构

Angular 2 Services

DEMO 源码

app.module.ts
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; // <-- NgModel lives here
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';

@NgModule({
  declarations: [
    AppComponent,
    HeroDetailComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
import { Component ,OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';

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' }
];

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

export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
  selectedHero: Hero;
  constructor(private heroService: HeroService) { };
  getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit(): void {
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}
app.component.html
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
<hero-detail [hero]="selectedHero"></hero-detail>
hero-detail.component.ts
import { Component, Input } from '@angular/core';
import { Hero } from './hero';

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

export class HeroDetailComponent {
    @Input() hero: Hero;
}
hero-detail.component.html
<div *ngIf="hero">
  <h2>{{hero.name}} details!</h2>
  <div><label>id: </label>{{hero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="hero.name" placeholder="hero.name" />
  </div>
</div>
hero.service.ts
import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
    getHeroes(): Promise<Hero[]> {
        return Promise.resolve(HEROES);
    }
}
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' }
];
hero.ts
export class Hero {
    id: number;
    name: string;
}

Angular 4.x 服务(Services)就只能带你到这里了。

 

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/angular-tutorial-part4/

Leave a Reply

Your email address will not be published. Required fields are marked *

评论 END