- Angular 8 教程
- Angular 8 - 主页
- Angular 8 - 简介
- Angular 8 - 安装
- 创建第一个应用程序
- Angular 8 - 架构
- Angular 组件和模板
- Angular 8 - 数据绑定
- Angular 8 - 指令
- Angular 8 - 管道
- Angular 8 - 响应式编程
- 服务和依赖注入
- Angular 8 - Http 客户端编程
- Angular 8 - 角度材料
- 路线和导航
- Angular 8 - 动画
- Angular 8 - 表单
- Angular 8 - 表单验证
- 认证与授权
- Angular 8 - 网络工作者
- Service Worker 和 PWA
- Angular 8 - 服务器端渲染
- Angular 8 - 国际化 (i18n)
- Angular 8 - 辅助功能
- Angular 8 - CLI 命令
- Angular 8 - 测试
- Angular 8 - Ivy 编译器
- Angular 8 - 使用 Bazel 构建
- Angular 8 - 向后兼容性
- Angular 8 - 工作示例
- Angular 9 - 有什么新变化?
- Angular 8 有用资源
- Angular 8 - 快速指南
- Angular 8 - 有用的资源
- Angular 8 - 讨论
Angular 8 - 服务和依赖注入
如前所述,服务在 Angular 应用程序中提供特定功能。在给定的 Angular 应用程序中,可能可以使用一项或多项服务。同样,Angular 组件可能依赖于一项或多项服务。
此外,Angular 服务可能依赖于其他服务才能正常工作。依赖关系解析是开发任何应用程序时复杂且耗时的活动之一。为了降低复杂性,Angular 提供了依赖注入模式作为核心概念之一。
本章让我们学习如何在 Angular 应用程序中使用依赖注入。
创建角度服务
Angular 服务是普通的 Typescript 类,具有一个或多个方法(功能)以及@Injectable装饰器。它使普通的 Typescript 类能够在 Angular 应用程序中用作服务。
import { Injectable } from '@angular/core'; @Injectable() export class DebugService { constructor() { } }
在这里,@Injectable装饰器将普通的 Typescript 类转换为 Angular 服务。
注册 Angular 服务
要使用依赖注入,每个服务都需要注册到系统中。Angular 提供了多种注册服务的选项。它们如下 -
- ModuleInjector @根级别
- ModuleInjector @平台级别
- 使用提供者元数据的 ElementInjector
- 使用 viewProviders 元数据的 ElementInjector
- 空注入器
模块注入器@root
ModuleInjector强制该服务仅在特定模块内使用。必须使用@Injectable中可用的ProvidedIn元数据来指定可以在其中使用该服务的模块。
该值应引用已注册的 Angular 模块之一(用@NgModule 装饰)。root是一个特殊选项,它指的是应用程序的根模块。示例代码如下 -
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class DebugService { constructor() { } }
ModuleInjector@平台
Platform Injector比ModuleInject高一级,仅在高级且罕见的情况下使用。每个 Angular 应用程序都通过执行PreformBrowserDynamic().bootstrap方法(参见main.js)开始,该方法负责引导 Angular 应用程序的根模块。
PreformBrowserDynamic()方法创建一个由PlatformModule配置的注入器。我们可以使用PlatformModule提供的platformBrowser()方法配置平台级服务。
空注入器
NullInjector比平台级ModuleInjector高一级,并且位于层次结构的顶层。我们无法在NullInjector中注册任何服务。当在层次结构中的任何位置都找不到所需的服务时,它会解决并简单地抛出错误。
使用提供者的 ElementInjector
ElementInjector强制该服务仅在某些特定组件内使用。@Component装饰器中可用的提供者和ViewProviders元数据用于指定特定组件可见的服务列表。使用提供程序的示例代码如下 -
费用条目列表组件
// import statement import { DebugService } from '../debug.service'; // component decorator @Component({ selector: 'app-expense-entry-list', templateUrl: './expense-entry-list.component.html', styleUrls: ['./expense-entry-list.component.css'], providers: [DebugService] })
在这里,DebugService仅在ExpenseEntryListComponent及其视图内可用。要在其他组件中创建 DebugService,只需在必要的组件中使用提供者装饰器即可。
使用 viewProviders 的 ElementInjector
viewProviders与Provider类似,只是它不允许在使用ng-content指令创建的组件内容内使用该服务。
费用条目列表组件
// import statement import { DebugService } from '../debug.service'; // component decorator @Component({ selector: 'app-expense-entry-list', templateUrl: './expense-entry-list.component.html', styleUrls: ['./expense-entry-list.component.css'], viewProviders: [DebugService] })
父组件可以通过其视图或内容使用子组件。下面提到了具有子视图和内容视图的父组件的示例 -
父组件视图/模板
<div> child template in view <child></child> </div> <ng-content></ng-content>
子组件视图/模板
<div> child template in view </div>
模板中父组件的使用(另一个组件)
<parent> <!-- child template in content --> <child></child> </parent>
这里,
- 子组件在两个地方使用。一个在父母的视野之内。另一个内部父内容。
- 服务将在子组件中可用,该子组件放置在父组件的视图中。
- 服务在子组件中不可用,该子组件放置在父组件的内容中。
解决 Angular 服务
让我们看看组件如何使用下面的流程图来解析服务。
这里,
- 首先,组件尝试查找使用viewProviders元数据注册的服务。
- 如果未找到,组件将尝试查找使用提供者元数据注册的服务。
- 如果找不到,组件会尝试查找使用ModuleInjector注册的服务
- 如果没有找到,组件会尝试查找使用PlatformInjector注册的服务
- 如果找不到,组件会尝试查找使用NullInjector注册的服务,这总是会抛出错误。
注入器的层次结构以及解析服务的工作流程如下 -
分辨率调节器
正如我们在上一章中了解到的,服务的解析从组件开始,并在找到服务或到达NUllInjector时停止。这是默认分辨率,可以使用分辨率修改器进行更改。它们如下 -
自己()
Self()启动和停止在当前ElementInjector本身中搜索服务。
import { Self } from '@angular/core'; constructor(@Self() public debugService: DebugService) {}
跳过自我()
SkipSelf()与 Self() 正好相反。它会跳过当前的 ElementInjector 并开始从其父ElementInjector搜索服务。
import { SkipSelf } from '@angular/core'; constructor(@SkipSelf() public debugService: DebugService) {}
主持人()
Host()停止在其主机ElementInjector中搜索服务。即使服务在更高级别上可用,它也会停止在主机上。
import { Host } from '@angular/core'; constructor(@Host() public debugService: DebugService) {}
选修的()
当搜索服务失败时,Optional()不会抛出错误。
import { Optional } from '@angular/core'; constructor(@Optional() private debugService?: DebugService) { if (this.debugService) { this.debugService.info("Debugger initialized"); } }
依赖注入器提供者
依赖注入器提供程序有两个目的。首先,它有助于为要注册的服务设置令牌。该令牌将用于引用和调用服务。其次,它有助于根据给定的配置创建服务。
如前所述,最简单的提供程序如下 -
providers: [ DebugService ]
在这里,DebugService既是令牌也是类,必须使用它来创建服务对象。提供者的实际形式如下 -
providers: [ { provides: DebugService, useClass: DebugService }]
这里,provides是令牌,useClass是创建服务对象的类引用。
Angular 提供了更多的提供者,它们如下:
别名类提供者
提供者的目的是重用现有的服务。
providers: [ DebugService, { provides: AnotherDebugService, userClass: DebugService }]
这里,只会创建一个DebugService服务实例。
价值提供者
值提供者的目的是提供值本身,而不是要求 DI 创建服务对象的实例。它也可以使用现有的对象。唯一的限制是对象应该采用引用服务的形式。
export class MyCustomService { name = "My Custom Service" } [{ provide: MyService, useValue: { name: 'instance of MyCustomService' }]
这里,DI提供者只是返回useValue选项中设置的实例,而不是创建新的服务对象。
非类依赖提供者
它允许在 Angular DI 中使用字符串、函数或对象。
让我们看一个简单的例子。
// Create the injectable token import { InjectionToken } from '@angular/core'; export const APP_CONFIG = new InjectionToken<AppConfig>('app.config'); // Create value export const MY_CONFIG: AppConfig = { title: 'Dependency Injection' }; // congfigure providers providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }] // inject the service constructor(@Inject(APP_CONFIG) config: AppConfig) {
工厂供应商
工厂提供商支持复杂的服务创建。它将对象的创建委托给外部函数。工厂提供者也可以选择设置工厂对象的依赖关系。
{ provide: MyService, useFactory: myServiceFactory, deps: [DebugService] };
此处,myServiceFactory返回MyService的实例。
角度服务使用
现在,我们知道如何创建和注册 Angular Service。让我们看看如何在组件内使用 Angular 服务。使用 Angular 服务就像将构造函数的参数类型设置为服务提供者的令牌一样简单。
export class ExpenseEntryListComponent implements OnInit { title = 'Expense List'; constructor(private debugService : DebugService) {} ngOnInit() { this.debugService.info("Angular Application starts"); } }
这里,
ExpenseEntryListComponent构造函数设置一个 DebugService 类型的参数。
Angular 依赖注入器(DI) 将尝试查找在应用程序中注册的 DebugService 类型的任何服务。如果找到,它将把 DebugService 的实例设置为 ExpenseEntryListComponent 组件。如果没有找到,就会抛出错误。
添加调试服务
让我们添加一个简单的调试服务,它将帮助我们在应用程序开发过程中打印调试信息。
打开命令提示符并转到项目根文件夹。
cd /go/to/expense-manager
启动应用程序。
ng serve
运行以下命令生成 Angular 服务DebugService。
ng g service debug
这将创建两个 Typescript 文件(调试服务及其测试),如下所示 -
CREATE src/app/debug.service.spec.ts (328 bytes) CREATE src/app/debug.service.ts (134 bytes)
我们来分析一下DebugService服务的内容。
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class DebugService { constructor() { } }
这里,
@Injectable装饰器附加到 DebugService 类,这使得 DebugService 能够在应用程序的 Angular 组件中使用。
providerIn选项及其值,root 使 DebugService 能够在应用程序的所有组件中使用。
让我们添加一个方法 Info,它将把消息打印到浏览器控制台中。
info(message : String) : void { console.log(message); }
让我们在ExpenseEntryListComponent中初始化服务并使用它来打印消息。
import { Component, OnInit } from '@angular/core'; import { ExpenseEntry } from '../expense-entry'; import { DebugService } from '../debug.service'; @Component({ selector: 'app-expense-entry-list', templateUrl: './expense-entry-list.component.html', styleUrls: ['./expense-entry-list.component.css'] }) export class ExpenseEntryListComponent implements OnInit { title: string; expenseEntries: ExpenseEntry[]; constructor(private debugService: DebugService) { } ngOnInit() { this.debugService.info("Expense Entry List component initialized"); this.title = "Expense Entry List"; this.expenseEntries = this.getExpenseEntries(); } // other coding }
这里,
DebugService 使用构造函数参数进行初始化。设置 DebugService 类型的参数 (debugService) 将触发依赖项注入来创建新的 DebugService 对象并将其设置到 ExpenseEntryListComponent 组件中。
在 ngOnInit 方法中调用 DebugService 的 info 方法会在浏览器控制台中打印消息。
可以使用开发人员工具查看结果,如下所示 -
让我们扩展应用程序来了解服务的范围。
让我们使用下面提到的命令创建一个DebugComponent 。
ng generate component debug CREATE src/app/debug/debug.component.html (20 bytes) CREATE src/app/debug/debug.component.spec.ts (621 bytes) CREATE src/app/debug/debug.component.ts (265 bytes) CREATE src/app/debug/debug.component.css (0 bytes) UPDATE src/app/app.module.ts (392 bytes)
让我们删除根模块中的 DebugService。
// src/app/debug.service.ts import { Injectable } from '@angular/core'; @Injectable() export class DebugService { constructor() { } info(message : String) : void { console.log(message); } }
在 ExpenseEntryListComponent 组件下注册 DebugService。
// src/app/expense-entry-list/expense-entry-list.component.ts @Component({ selector: 'app-expense-entry-list', templateUrl: './expense-entry-list.component.html', styleUrls: ['./expense-entry-list.component.css'] providers: [DebugService] })
在这里,我们使用提供者元数据(ElementInjector)来注册服务。
打开DebugComponent (src/app/debug/debug.component.ts) 并导入DebugService并在组件的构造函数中设置一个实例。
import { Component, OnInit } from '@angular/core'; import { DebugService } from '../debug.service'; @Component({ selector: 'app-debug', templateUrl: './debug.component.html', styleUrls: ['./debug.component.css'] }) export class DebugComponent implements OnInit { constructor(private debugService: DebugService) { } ngOnInit() { this.debugService.info("Debug component gets service from Parent"); } }
这里,我们还没有注册DebugService。因此,如果用作父组件,则 DebugService 将不可用。当在父组件内部使用时,如果父组件有权访问该服务,则可以从父组件获得该服务。
打开ExpenseEntryListComponent模板 (src/app/expense-entry-list/expense-entry-list.component.html) 并包含内容部分,如下所示:
// existing content <app-debug></app-debug> <ng-content></ng-content>
在这里,我们包含了内容部分和 DebugComponent 部分。
让我们将调试组件作为内容包含在 AppComponent 模板的ExpenseEntryListComponent组件内。打开AppComponent模板并更改app-expense-entry-list如下 -
// navigation code <app-expense-entry-list> <app-debug></app-debug> </app-expense-entry-list>
在这里,我们将DebugComponent作为内容包含在内。
让我们检查应用程序,它将在页面末尾显示DebugService模板,如下所示 -
此外,我们还可以在控制台中的调试组件中看到两条调试信息。这表明调试组件从其父组件获取服务。
让我们更改将服务注入ExpenseEntryListComponent的方式以及它如何影响服务的范围。将提供程序注入器更改为 viewProviders 注入。viewProviders不会将服务注入到内容子项中,因此它应该会失败。
viewProviders: [DebugService]
检查应用程序,您将看到调试组件之一(用作内容子组件)抛出错误,如下所示 -
让我们删除模板中的调试组件并恢复应用程序。
打开ExpenseEntryListComponent模板 (src/app/expense-entry-list/expense-entry-list.component.html) 并删除以下内容
<app-debug></app-debug> <ng-content></ng-content>
打开AppComponent模板并更改app-expense-entry-list如下 -
// navigation code <app-expense-entry-list> </app-expense-entry-list>
将viewProviders设置更改为ExpenseEntryListComponent中的提供者。
providers: [DebugService]
重新运行应用程序并检查结果。