依赖性注入在Angular中如何工作

109 阅读7分钟

在软件开发中,有几种处理依赖注入的拟议模式。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 方法中调用productServicegetProducts 方法,并将返回值分配给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应用程序中。

当跨子组件共享依赖关系时,依赖关系在提供依赖关系的组件的所有子组件中共享。它们可以随时被注入到子组件的构造器中,使每个子组件都能重复使用来自父组件的同一个服务实例。

假设我们想显示一个最近添加的产品列表。很明显,显示最近添加的产品列表的模型与显示所有产品的模型相同。因此,我们可以在ProductListComponentRecentProductComponent 组件之间共享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.