Angular的初学者指南

258 阅读8分钟

大家好!我希望你一直在关注我们关于Angular组件路由的教程。在这篇文章中,我们将继续讨论Angular中另一个有趣的概念:服务。

如果Angular组件是我们应用程序的表现层,那么什么会负责实际获取真实数据和执行业务逻辑呢?这正是Angular服务出现的地方。Angular服务的作用是获取、组织并最终在组件间共享数据、模型和业务逻辑。

在我们深入了解Angular服务的技术细节之前,让我们更多地了解它的特点。这将有助于你理解哪部分代码需要放在组件内,哪部分需要放在Angular服务内。

这里有一些关于服务的重要事实。

一个服务是用@Injectable 装饰器定义的。这告诉Angular,该服务可以被注入到组件或其他服务中。我们将在后面讨论更多关于注入服务的问题。

服务是一个容纳你所有业务逻辑的地方,并在各组件之间共享它。这使你的应用程序更具可扩展性和可维护性。通常,服务也是与后端交互的正确位置。例如,如果你需要进行AJAX调用,完成调用的方法可以在一个服务中进行。

服务是单子类。在你的Angular应用程序中,你将只有一个特定服务的单一实例在运行。

什么是服务?

Angular中的服务是对象,在应用程序的生命周期中只被实例化一次。一个服务所接收和维护的数据可以在整个应用程序中使用。这意味着组件可以在任何时候从一个服务中获取数据。依赖性注入 被用来在组件内引入服务。

让我们试着了解如何创建一个服务并在Angular组件中使用它。你可以在我们的GitHub repo中找到这个项目的完整源代码

一旦你有了源代码,请导航到项目目录,使用npm install 安装所需的依赖项。依赖项安装完毕后,通过输入以下命令启动应用程序。

ng serve

你应该让应用程序运行在 [https://localhost:4200/](https://localhost:4200/).

我们项目的整体文件夹结构将如下。

src
--app
----components
------employee.component.css
------employee.component.html
------employee.component.ts
----services
------employee.service.spec.ts
------employee.service.ts
------employeeDetails.service.ts
--app.routing.module.ts
--app.component.css
--app.component.html
--app.component.spec.ts
--app.component.ts
--app.module.ts
--assets
--index.html
--tsconfig.json

1.构建服务的骨架

在Angular中创建一个服务有两种方法。

  1. 手动在项目内创建文件夹和文件。
  2. 使用ng g service <path/service_name> 命令来自动创建一个服务。当你使用这种方法时,你会在选择的目录中自动得到一个**.service.ts和一个.service.spec.ts**文件。
ng g service components/employee 

2.创建服务

现在.service.ts文件已经在你的项目结构中被创建,是时候填充服务的内容了。要做到这一点,你必须决定该服务需要做什么。记住,你可以有多个服务,每个服务都要执行一个特定的业务操作。在我们的例子中,我们将使用employee.service.ts**来向任何使用它的组件返回一个静态的角色列表。

employee.service.ts中输入以下代码。

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

@Injectable({
  providedIn: 'root',
})

export class EmployeeService {
  role = [
    {'id':'1', 'type':'admin'},
    {'id':'2', 'type':'engineer'},
    {'id':'3', 'type':'sales'},
    {'id':'4', 'type':'human_resources'}
  ]
    getRole(){
    return this.role;
  }
}

这个服务只是向应用程序返回一个静态的角色列表。让我们逐行解读这个服务。

  1. 我们从@angular/core 库中导入Injectable 。这很关键,因为我们的服务将被使用或注入到组件中。@Injectable 指令允许我们识别服务。
  2. 接下来,我们应用@Injectable 装饰器。@InjectableprovidedIn 属性指定了注入器将在哪里可用。大多数情况下,root 被指定为它的值。这意味着服务可以在应用层面被注入。其他选项是:anyplatformnull 、或Type<any>
  3. 我们创建一个类组件,名称为EmployeeService 。这个类有一个方法getRole ,它返回一个静态的对象数组。

3.创建一个组件

如前所述,Angular中的服务是用来保存应用程序的业务逻辑的。为了向浏览者显示数据,我们需要一个表现层。这就是传统的基于类的Angular组件出现的地方,使用装饰器@Component

你可以在我这个系列的前一篇文章中了解更多关于Angular组件的信息。它将帮助你理解Angular组件并创建你自己的组件。创建文件employee.component.ts,并在其中添加以下代码。

import { Component, OnInit } from '@angular/core';
import { EmployeeService } from '../services/employee.service';

@Component({
  selector: 'employee',
  templateUrl: './employee.component.html'
})

export class EmployeeComponent implements OnInit {

    role: any;
    
    constructor(private employeeService: EmployeeService) {		
	}
    
    ngOnInit(): void {
        this.role = this.employeeService.getRole()
    }
 
}

让我们把它分解开来。

  1. 导入@Component 装饰器并调用它。我们指定'employee' 作为选择器,并提供一个模板URL,指向描述该组件视图的HTML。
  2. 声明组件类并指定它实现了OnInit 。因此,我们可以定义一个ngOnInit 事件处理程序,当该组件被创建时,它将被调用。
  3. 为了使用我们的服务,它必须在构造函数中被声明。在我们的例子中,你将在构造函数中看到private employeeService: EmployeeService 。通过这一步,我们将使该服务在整个组件中被访问。
  4. 因为我们的目标是在创建雇员组件时加载角色,所以我们在ngOnInit 中获取数据。

这还能再简单点吗?由于该服务是一个单子类,它可以在多个组件中重复使用而不影响性能。

4.创建一个视图

现在我们的组件中已经有了数据,让我们建立一个简单的employee.component.html 文件来遍历角色并显示它们。下面,我们使用*ngFor 来迭代角色,并只向用户显示类型。

<h3>Data from employee.service</h3>
<ul>
    <li *ngFor = "let role of roles">{{role.type}}</li>
</ul>

5.运行项目

在项目启动和运行之前,我们只有一个步骤。我们需要确保employee.component.ts 文件被包含在我们的声明列表中,在@NgModule 指令中。

如下图所示,EmployeeComponent 被添加到app.module.ts 文件中。

//app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { EmployeeComponent } from './components/employee.component';

@NgModule({
  declarations: [
    AppComponent,
    EmployeeComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

有趣的是,我们没有在我们的提供者列表中添加该服务,但我们却能成功地使用该服务。为什么呢?因为我们已经指定该服务要在应用程序的根层提供(即用providedIn: 'root' 参数)。然而,继续阅读以了解更多关于我们确实需要在@NgModuleproviders 数组中提到一个服务的情况。

另外,我们需要将employee 元素添加到app.component.html文件中。

<h1>
  Tutorial: Angular Services
</h1>
<employee></employee>

<router-outlet></router-outlet>

如果我们到目前为止运行我们的应用程序,它将看起来像这样。

screenshot of the completed app

6.从服务中动态地获取数据

现在,我们要获取特定于我们的employee.component.ts的数据。

让我们创建一个新的服务来从API中获取数据。

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class EmployeDetailsService {
    fetchEmployeeDetailsURL = 'https://reqres.in/api/users?page=2'
    constructor(private http: HttpClient) { }
    fetchEmployeeDetails = () => {
        return this.http.get(this.fetchEmployeeDetailsURL);
    }
}

现在,让我们逐行理解我们的代码。

  1. 因为我们想通过AJAX调用来获取数据,所以导入HttpClient 是很重要的。如果你是第一次使用HttpClient ,你可以在本系列的另一篇文章中了解更多关于它的信息。
  2. 在我们的EmployeeDetailsService ,我们没有指定provideIn 参数。这意味着我们需要做一个额外的步骤来让整个应用程序知道我们的可注射服务。你将在下一步中了解到这一点。
  3. HttpClient 本身就是一个可注入的服务。在构造函数中声明它,这样它就会被注入到组件中。在fetchEmployeeDetails 方法中,我们将使用HttpClient.get 方法来从URL中获取数据。

7.在app.module中注册服务

与我们的第一个服务不同,我们在app.module.ts中注册EmployeeDetailsService ,这是至关重要的,因为我们没有在根层声明可注入的东西。下面是更新后的app.module.ts文件。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { EmployeeComponent } from './components/employee.component';
import { EmployeDetailsService } from './services/employeeDetails.service';

@NgModule({
  declarations: [
    AppComponent,
    EmployeeComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [
    EmployeDetailsService],
  bootstrap: [AppComponent]
})
export class AppModule { }

如果你密切关注,你可能已经注意到两个重要的变化。

  1. 在我们的app.module.ts 文件中,我们需要在Providers 的列表中包括EmployeDetailsService
  2. 我们需要从@angular/common/http 中导入HttpClientModuleHttpClientModule 必须包含在我们的imports 列表中。

就是这样--我们现在已经准备好在我们的组件中使用EmployeeDetailsService

8.获取动态数据

为了适应新的服务,我们要对我们的组件做一些改变。

添加一个按钮来加载数据

首先,我们将在我们的视图中添加一个按钮。当我们点击这个按钮时,数据将通过AJAX调用加载。下面是更新后的employee.component.html 文件。

<h3>Data from employee.service</h3>
<ul>
    <li *ngFor = "let role of roles">{{role.type}}</li>
</ul>
<button (click)="loadEmployeeDetails()">Load Employee Details</button>
<ul>
    <li *ngFor = "let employee of employeeDetails">{{employee.email}}</li>
</ul>

订阅获取器函数

接下来,在EmployeDetailsService 中订阅getter函数。为了实现这一点,我们将在employee.component.ts中的构造函数中加入EmployeDetailsService

import { Component, OnInit } from '@angular/core';
import { EmployeeService } from '../services/employee.service';
import { EmployeDetailsService } from '../services/employeeDetails.service';

@Component({
  selector: 'employee',
  templateUrl: './employee.component.html'
})

export class EmployeeComponent implements OnInit {

    roles: any;
    employeeDetails: any;
    constructor(private employeeService: EmployeeService,
        private employeeDetailsService: EmployeDetailsService) {		
	}
    ngOnInit(): void {
        this.roles = this.employeeService.getRole()
    }

    loadEmployeeDetails = () => {
        this.employeeDetailsService.fetchEmployeeDetails()
                                    .subscribe((response:any)=>{
                                        this.employeeDetails = response.data;
                                    })
    }
 
}

有了这个改动,在点击LoadEmployeeDetails 按钮时,我们会看到下面的视图。

screenshot of the completed app

结论

就这样吧!我们已经逐步建立了一个可以处理静态和动态数据的Angular服务。现在,你应该能够建立你自己的Angular服务,并使用它们通过AJAX调用来获取数据。而且你甚至可以用更多的可重用方式实现你的业务逻辑。