在软件开发中,有几种处理依赖注入的拟议模式。Angular执行构造器注入模式,它使用构造器将类的依赖关系作为构造器的参数传入。
Angular有自己内置的依赖注入(DI)框架,在实例化时向类提供依赖关系。这是在Angular中构建可扩展Web应用的一个重要功能。
在本教程中,我们将通过一些实际例子向你展示Angular中的依赖注入是如何工作的。我们还将回顾一些最佳实践,并讨论在Angular应用中处理依赖注入的几种不同方法。
要跟上进度,你应该具备以下条件。
- Node.js V10.x
- 有Angular的工作知识
- 对TypeScript有初步了解
什么是Angular中的依赖注入?
根据Angular的官方文档,依赖注入是 "一种设计模式,其中一个类从外部来源请求依赖,而不是创建它们。"
简而言之,Angular的依赖注入旨在将服务的实现与组件解耦。这便于测试、重写和改变服务,而不影响依赖这些服务的组件。
大多数时候,你会遇到一些Angular教程或代码库是这样处理依赖注入的。
ng generate service products/product
上述命令创建了一个新的Angular服务以及其他附带的文件。
//app/products/product.model.ts
export interface Product {
id: number;
name: string;
}
上面的片段使用TypeScript接口来创建一个模型,以验证从产品服务返回的数据。
//app/products/product.service.ts
import { Injectable } from '@angular/core';
import { Product } from './product.model';
@Injectable({
providedIn: 'root'
})
export class ProductService {
constructor() { }
getProducts(): Product[] {
return [
{ id: 1, name: 'Nike' },
{ id: 2, name: 'Balenciaga' },
{ id: 3, name: 'Gucci' },
{ id: 4, name: 'Addidas' },
];
}
}
providedIn 属性为该服务创建了一个提供者。在这种情况下,providedIn: 'root' ,指定Angular应该在根注入器中提供服务(即让它在整个应用程序中可用)。
现在,ProductService 可以被注入到我们应用程序的任何地方。
//app/products/product-list/product-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Product } from '../product.model';
import { ProductService } from '../product.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css'],
providers: [ProductService]
})
export class ProductListComponent implements OnInit {
products: Product[];
private productService: ProductService;
constructor() {
this.productService = new ProductService();
}
ngOnInit(): void {
this.products = this.productService.getProducts();
}
}
上面的片段使用组件构造函数中的new关键字实例化了productService 私有属性,在ngOnInit 方法中调用productService 的getProducts 方法,并将返回值分配给products 属性。
//app/products/product-list/product-list.component.html
<h3>Our products</h3>
<ul>
<li *ngFor="let product of products">
<p>(S/N: {{product.id}}) {{product.name}} </p>
</li>
</ul>
上面的片段使用ngFor 指令来显示产品列表。
如果你使用ng serve 指令运行该应用程序,一切都应该运行良好。
尽管我们通过Angular服务成功地将我们的组件与产品逻辑解耦,这也是DI的主要目的,但这种方法仍然有两个主要的缺点。首先是每次渲染ProductListComponent ,都要创建一个新的服务。这可能会对应用程序的性能产生负面影响,因为在这种情况下,人们期望有一个单子服务。
其次,如果我们改变ProductService 的构造函数以适应另一个依赖关系,我们也需要改变ProductListComponent 的构造函数的实现。这意味着该组件仍然与服务的实现紧密耦合,这可能会使服务的测试变得非常困难。
在Angular中处理依赖注入的最佳实践是如下的。
更新product-list.component.ts ,如下图所示。
//app/products/product-list/product-list.component.ts
...
export class ProductListComponent implements OnInit {
products: Product[];
constructor(private productService: ProductService) { }
ngOnInit(): void {
this.products = this.productService.getProducts();
}
这样一来,组件不需要知道如何实例化服务。相反,它接收依赖关系并通过其构造函数注入。这种方法使测试服务变得更容易。
如何处理Angular中的依赖性注入
在Angular应用程序中处理依赖性注入时,你可以采取基于应用程序或基于组件的方法。让我们来看看两者的区别。
基于应用的依赖性注入
Angular DI框架通过提供一个保持应用程序所需的所有依赖性列表的注入器,使依赖性在整个应用程序中可用。当一个组件或服务想要使用一个依赖关系时,注入器首先检查它是否已经创建了该依赖关系的实例。如果没有,它就创建一个新的实例,将其返回给组件,并保留一个副本供进一步使用,这样在下次请求相同的依赖关系时,它就会返回保留的依赖关系,而不是创建一个新的。
在Angular应用程序中,有与注入器相关的层次结构。每当Angular组件在其构造函数中定义了一个令牌,注入器就会在注册的提供者池中搜索与该令牌相匹配的类型。如果没有找到匹配的,它就委托父组件的提供者通过组件注入器树向上进行搜索。如果它找到了依赖关系,它就会停止,并将它的一个实例返回给请求它的组件。
如果提供者查询结束时没有匹配,它将返回到请求提供者的组件的注入器,并在模块注入器的层次结构中搜索所有父模块的注入器,直到它到达根注入器。如果没有找到匹配,Angular会抛出一个异常。否则,它将返回该组件上的依赖实例。
我们已经走过了这种方法的一些实际代码片段。如果你想复习的话,请随时参考上一节。
基于组件的依赖性注入
这种方法被称为直接将依赖注入到组件树中,使用@Component 装饰器的providers 属性来向组件注入器注册服务。这种方法常用于Angular应用程序中。
当跨子组件共享依赖关系时,依赖关系在提供依赖关系的组件的所有子组件中共享。它们可以随时被注入到子组件的构造器中,使每个子组件都能重复使用来自父组件的同一个服务实例。
假设我们想显示一个最近添加的产品列表。很明显,显示最近添加的产品列表的模型与显示所有产品的模型相同。因此,我们可以在ProductListComponent 和RecentProductComponent 组件之间共享products 的依赖关系(服务)。
在products 模块内创建一个名为recent-products 的新组件,命令如下。
ng generate component products/recentProducts --module=products
更新recent-products.component.ts ,如下所示。
import { Component, OnInit } from '@angular/core';
import { ProductService } from '../product.service';
import { Product } from '../product.model';
@Component({
selector: 'app-recent-products',
templateUrl: './recent-products.component.html',
styleUrls: ['./recent-products.component.css']
})
export class RecentProductsComponent implements OnInit {
products: Product[];
constructor(private productService: ProductService) { }
ngOnInit(): void {
this.products = this.productService.getHeroes();
}
}
在这里,我们将ProductService 注入到RecentProductsComponent 的构造器中,而没有像对ProductListComponent 那样,通过@component 装饰器的providers 属性来实际提供它。
我们如何解释@component 装饰器中缺少的providers 属性?没有这个,RecentProductsComponent 将不知道如何创建ProductService 的实例。
更新ProductListComponent 模板如下。
//app/products/product-list/product-list.component.html
<h3>Our products</h3>
<ul>
<li *ngFor="let product of products">
<p>(S/N: {{product.id}}) {{product.name}} </p>
</li>
</ul>
<app-recent-products></app-recent-products>
我们将通过使RecentProductComponent 成为ProductListComponent 的直接子节点来回答前面的问题,使RecentProductComponent 能够访问ProductListComponent 提供的所有依赖关系。
更新recent-products.component.html ,如下所示。
//app/products/recent-products/recent-products.component.html
<h3>Recent Products</h3>
<ul>
<li *ngFor="let product of products | slice:0:5">
{{product.name}}
</li>
</ul>
在这里,我们对ngFor 语句应用分片管道,只显示前五个产品。
当你用ng serve 命令运行应用程序时,你应该看到浏览器中呈现的所有产品和最近产品的列表。
总结
在本教程中,我们建立了对Angular依赖注入的基本理解。我们通过几个实际的例子,展示了依赖关系是如何在子组件以及整个应用中共享的。我们还回顾了在你的下一个Angular应用中实现依赖注入的一些最佳实践。
The postHow dependency injection works in Angularappeared first onLogRocket Blog.