什么是 Angular 服务?

2025年3月17日 | 阅读11分钟

服务是具有特定用途的可重用代码片段。它是在应用程序的多个组件中使用的代码。

我们的组件需要访问数据。你可以在每个组件中编写数据访问代码,但这效率很低,并且违反了单一职责原则。组件应该专注于向用户呈现数据。

从后端服务器接收数据的任务应该委托给另一个类。我们称之为服务类,因为它为每个组件提供所需的数据。

Angular 服务有什么用?

独立于组件的功能,如日志服务

  • 在组件之间共享逻辑或数据
  • 封装外部交互,如数据访问
  • 服务更容易测试。
  • 它们更容易调试。
  • 我们可以在许多地方重用服务。

如何在 Angular 中创建服务

Angular 服务只是一个 JavaScript 函数。我们所要做的就是创建一个类并添加方法和属性。然后我们可以在我们的组件中创建这个类的实例并调用它的方法。

服务最好的用途之一是从数据源获取数据。让我们创建一个简单的服务,它接收产品数据并将其发送到我们的组件。

产品模型

请在文件夹 src/app 下创建一个新文件,并将其命名为 product.ts

product.ts

上面的 Product 类是我们的领域模型。

产品 Angular 服务

接下来,我们创建一个 Angular 服务,它返回产品列表。

请在 src/app 文件夹下创建一个新文件,并将其命名为 product.service.ts。product.service.ts

首先,我们将 Product 模型设置为 product.ts。从其中导入

接下来,创建一个 ProductService 类并将其导出。我们需要导出它,以便组件和其他服务类可以导入并使用它。

Get Products 方法返回产品集合。在此示例中,我们硬编码了产品。在实际生活中,你会向后端 API 发送 HTTP GET 请求以获取数据,服务已准备就绪。

请注意,上面的类是一个简单的 JavaScript 函数。它与 Angular 无关。

调用 ProductService

下一步是从组件调用 ProductService。打开 app.componet.ts 并添加以下代码。

app.component.ts

我们从导入 Product 和 ProductService 开始

我们在 AppComponent 的构造函数中创建了一个 ProductService 实例。在实际的 Angular 应用程序中,我们使用 Angular 中的依赖注入将 ProductService 注入到构造函数中。我们将在下一个教程中学习这一点。

getProducts 方法调用 ProductService 的 getProducts 方法。它返回一个产品列表,我们将其存储在局部变量 products 中。

What is an Angular Service

将服务注入组件

在此示例中,我们直接在组件中实例化了产品服务,如下所示

如上所示,实例化服务有几个缺点。

  • 产品服务与组件紧密耦合。如果我们更改 ProductService 类的定义,我们必须更新使用该服务的所有代码。
  • 如果我们要用更好的服务替换产品服务,我们必须搜索所有使用产品服务的地方并手动替换它。
  • 使测试变得困难。我们可能需要为测试提供伪造的产品服务,并为生产使用产品服务。

我们可以通过使用 Angular 依赖注入来解决这个问题。

添加服务

英雄之旅英雄组件目前正在接收和显示假数据。

在本教程中重构后,HeroesComponent 将变得精简并专注于支持视图。使用模拟服务进行单元测试也更容易。

有关此页面上描述的示例应用程序,请参阅实时示例/下载示例。

服务是在彼此不了解的类之间共享信息的好方法。你将创建一个 MessageService 并将其注入两个位置。

  • 注入到 HeroService 中,它使用服务发送消息。
  • 注入到 MessagesComponent 中,当用户单击英雄时,它会显示消息 ID。

创建英雄服务

使用 Angular CLI 创建一个名为 Hero 的服务。

该命令在 src/app/hero.service.ts 中生成一个骨架 HeroService 类,如下所示

请注意,新服务导入了 Angular 的可注入符号,并使用 @Injectable() 装饰器注释了该类。它将该类标记为参与依赖注入机制。

HeroService 类将提供可注入服务,并且也可能具有其可注入依赖项。它还没有任何依赖项,但很快就会有。

@Injectable() 装饰器接受服务的元数据对象,就像 @Component() 装饰器为组件类所做的那样。

获取英雄数据

HeroService 可以从任何地方获取英雄数据——Web 服务、本地存储或模拟数据源。

从组件中删除数据访问意味着你可以随时更改实现的思想,而无需触及任何组件。它们不知道服务是如何工作的。

导入 Hero 和 HEROES。

提供 HeroService

通过注册提供程序,你需要在 Angular 将 HeroService 注入 HeroesComponent 之前使其可用于依赖注入系统。提供程序可能会创建或交付服务;在此示例中,它实例化 HeroService 类以提供服务。

为确保 HeroService 可以提供此服务,请将其注册到 Injector,Injector 是负责在应用程序需要时选择和注入提供程序的对象。

默认情况下,Angular CLI 命令 ng service 通过在 @Injectable() 装饰器中包含提供的提供程序元数据:“root”,将提供程序注册到服务的根注入器。

当你在根级别提供服务时,Angular 会创建一个 HeroService 的单一共享实例,并将其注入到任何请求它的类中。在 @Injectable 元数据中注册提供程序允许 Angular 通过在服务不再使用时将其删除来定制应用程序。

HeroService 现在已准备好插入 HeroesComponent。

这是一个临时代码示例,它将允许你提供和使用 HeroService。该代码将从“最终代码审查”中的英雄服务中分离出来。

更新英雄组件

打开 HeroesComponent 类文件。

删除 HEROES 导入,因为你不再需要它了。改用 HeroService。

将 Hero 属性的定义替换为声明。

注入 HeroService

向构造函数添加一个类型为 HeroService 的私有 heroService 参数。

该参数同时定义了一个私有 heroService 属性,并将其标识为 HeroService 注入点。

当 Angular 创建 HeroesComponent 时,依赖注入系统将 heroService 参数设置为 HeroService 的单例实例。

添加 getHeroes()

创建一个从服务中检索英雄的方法。

在 ngOnInit() 中调用它

虽然你可以在构造函数中调用 getHeroes(),但这不是最佳实践。

将构造函数保留用于最小初始化,例如将构造函数参数连接到属性。构造函数不应执行任何操作。它当然不应该调用向远程服务器发出 HTTP 请求的函数,就像实际的数据服务一样。

相反,在 ngOnInit 生命周期挂钩内部调用 getHeroes(),让 Angular 在 HeroesComponent 实例创建后在适当的时候调用 ngOnInit()。

浏览器刷新后,应用程序应该像以前一样运行,显示英雄列表,并在你单击英雄名称时显示英雄详细信息视图。

可观察数据

HeroService.getHeroes() 方法具有 同步签名, 这意味着 HeroService 可以同步获取英雄。HeroesComponent 消耗 getHeroes() 结果,就好像英雄可以同步获取一样。

这在实际应用程序中不起作用。现在你可以避免这种情况,因为服务当前返回的是假英雄。但很快,应用程序将从远程服务器获取英雄,这是一个本质上异步的操作。

HeroService 必须等待服务器响应,getHeroes() 可能不会立即返回英雄数据,并且在服务等待时浏览器不会阻塞。

HeroService.getHeroes() 将返回一个 Observable,因为它最终将使用 Angular HttpClient.get 方法获取英雄,而 HttpClient.get() 返回一个 Observable。

可观察的 HeroService

Observable 是 RxJS 库的主要类之一。

在稍后的 HTTP 教程中,你将了解到 Angular 的 HttpClient 方法返回 RxJS Observable。本教程将模拟 RxJS 的 of() 函数以从服务器获取数据。

打开 HeroService 文件并从 RxJS 导入 Observables 和符号。

content_copyimport { Observable, of } from 'rxjs';

将 getHeroes() 方法替换为以下内容

Of (HEROES) 返回一个 Observable它发出 单个值, 即模拟英雄数组。

在 HTTP 教程中,你将调用 HttpClient.get< Hero []>(),它也返回一个 Observable< Hero []>,它发出 单个值, 即 HTTP 响应正文中的英雄数组。

在 HeroesComponent 中订阅

HeroService.getHeroes 方法曾经返回一个 Hero[]。 现在它返回一个 Observable

你必须适应 HeroesComponent 中的这种差异。

找到 getHeroes 方法并将其替换为以下代码。

Observable.subscribe() 是关键区别。

以前的版本为组件的 Heroes 属性提供了一个英雄数组。当服务器可能立即返回英雄时,或者浏览器 UI 可能在等待服务器响应时冻结时,会发生赋值。

新版本等待 Observable 发出英雄数组,这可能在几分钟后发生。subscribe() 方法将发出的数组传递给回调,设置组件的 heroes 属性。

当 HeroService 从服务器请求英雄时,这种异步方法将起作用。

本节包含以下内容

  • 消息添加组件,在屏幕底部显示应用程序消息
  • 创建一个可注入的、应用程序范围的消息服务,用于发送要显示的消息
  • 将 MessageService 注入 HeroService
  • 当 HeroService 成功获取英雄时显示消息

创建消息组件

使用 CLI 创建 MessagesComponent。content_copying 组件生成消息

CLI 在 src/app/messages 文件夹中创建组件文件,并在 AppModule 中声明 MessagesComponent。

修改 AppComponent 模板以显示生成的 MessagesComponent。

src/app/app.component.html

你应该在页面底部看到 MessagesComponent 的默认段落。

创建 MessageService

  • 使用 CLI 在 src/app 中创建 MessageService。
  • content_copyng generate service message

打开 MessageService 并将其内容替换为以下内容。

src/app/message.service.ts

该服务公开其消息缓存和两个方法:一个用于向缓存中 add() 消息,另一个用于 clear() 缓存。

将其注入 HeroService

在 HeroService 中,导入 MessageService。

src/app/hero.service.ts(导入 MessageService)

使用声明私有 messageService 属性的参数修改构造函数。当 Angular 创建 HeroService 时,它会将单例 MessageService 注入到该属性中。

src/app/hero.service.ts

这是典型的 “服务中服务” 场景:你将 MessageService 注入到 HeroService 中,而 HeroService 又注入到 HeroesComponent 中。

从 HeroService 发送消息

修改 getHeroes() 方法,以便在获取英雄时发送消息。

src/app/hero.service.ts

从 HeroService 显示消息

MessagesComponent 应该显示所有消息,包括 HeroService 在获取英雄时发送的消息。

打开 MessagesComponent 并导入 MessageService。

使用声明公共消息属性的参数修改构造函数。当它创建 MessagesComponent 时,Angular 会将单例 MessageService 注入到该属性中。

src/app/messages/messages.component.ts

messageService 属性 必须是公共的, 因为你将把它绑定到模板中。

Angular 只绑定 到公共 组件属性。

绑定到 MessageService

将 CLI 生成的 MessagesComponent 模板替换为以下内容。

src/app/messages/messages.component.html

此模板直接连接到组件的消息。

  • *NGIF 仅当有消息要显示时才显示消息区域。
  • *ngFor 反复显示消息列表
    元素。
  • Angular 事件绑定将按钮的点击事件绑定到 MessageService.clear()。

当你将私有 CSS 样式添加到 messages.component.css 时,消息会看起来更好,如下面的“最后代码审查”选项卡之一所示。

向英雄服务添加更多消息。

以下示例展示了如何在用户每次点击英雄时发送和显示消息,显示用户选择的历史记录。当你进入路由的下一部分时,这将很有帮助。

更多关于此源文本的信息需要额外翻译信息

src/app/heroes/heroes.component.ts

刷新浏览器以查看英雄列表,然后向下滚动以查看 HeroService 的消息。每次你点击英雄时,都会出现一条新消息来记录选择。使用“清除消息”按钮清除消息历史记录。

最终代码审查

以下是页面上讨论的代码文件。

总结

你已在 HeroService 类中重构了数据访问。你已将 HeroService 作为其服务提供程序注册到根级别,以便在应用程序中的任何位置插入。你使用 Angular 依赖注入将其注入到组件中。你已为 HeroService 提供了异步签名获取数据方法。你发现了 Observable 和 RxJS Observable 库。

你使用 RxJS of() 返回模拟英雄的 Observable (Observable)。组件的 ngOnInit 生命周期挂钩调用 HeroService 方法,而不是构造函数。你已创建 MessageService,用于类之间松散耦合的通信。


下一主题RxJS 库