Home  >  Article  >  Web Front-end  >  Let's talk about the basic concepts in Angular's ​​dependency injection system

Let's talk about the basic concepts in Angular's ​​dependency injection system

青灯夜游
青灯夜游forward
2022-03-03 11:06:051968browse

This article will talk about Angular and introduce the basic concept of dependency injection. I hope it will be helpful to everyone!

Let's talk about the basic concepts in Angular's ​​dependency injection system

As a front-end framework designed “for large-scale front-end projects”, Angular actually has many designs worthy of reference and learning. This series is mainly used to study these designs and functions. Realization principle. This article focuses on the biggest feature of Angular - dependency injection. First, we will introduce some basic concepts in the Angular dependency injection system.

Dependency Injection

Since we want to introduce the dependency injection design of the Angular framework, let’s first lay out the basic concepts of dependency injection. We often confuse the concepts of Dependency Inversion Principle (DIP), Inversion of Control (IoC), and Dependency Injection (DI), so we will briefly introduce them here. [Related tutorial recommendations: "angular tutorial"]

Dependency inversion principle, control inversion, dependency injection

Low coupling, high cohesion It is probably one of the design goals of every system, and many design patterns and concepts have been produced for this purpose, including the design ideas of dependency inversion principle and control inversion.

(1) Dependency Inversion Principle (DIP).

The original definition of the dependency inversion principle is:

  • High-level modules should not depend on low-level modules, both should rely on their abstractions;
  • Abstractions should not You should rely on details, and details should rely on abstractions.

To put it simply: modules should not depend directly on each other, but should rely on an abstract rule (interface or abstract class).

(2) Inversion of Control (IoC).

The definition of inversion of control is that the dependencies between modules are instantiated and managed from within the program to the outside. That is, when an object is created, it is controlled by an external entity that controls all objects in the system, and the references of the objects it depends on are passed (injected) to it.

There are two main ways to implement inversion of control:

  • Dependency injection: passively receiving dependent objects
  • Dependency lookup: actively requesting dependent objects

(3) Dependency injection.

Dependency injection is the most common technique of control inversion.

Dependency inversion and control inversion complement each other and can often be used together to effectively reduce the coupling between modules.

Dependency Injection in Angular

In Angular, dependency injection technology is also used. When instantiating a class, the DI framework will provide it with the dependencies declared by this class. (Dependencies: refer to the services or objects that a class needs to perform its functions).

Dependency injection in Angular basically revolves around components or modules, and is mainly used to provide dependencies for newly created components.

The main dependency injection mechanism in Angular is the injector mechanism:

  • Any dependency required in the application must use the application's injector. Register a provider so that the injector can use this provider to create new instances
  • Angular will create the full application-level injector and other injectors required during startup

This mainly involves two concepts, namely Injector injector and Provider provider, let’s take a look.

Injector Injector

The Injector injector is used to create dependencies, maintain a container to manage these dependencies, and reuse them as much as possible. The injector will provide a singleton of the dependency and inject this singleton object into multiple components.

Obviously, as a container used to create, manage, and maintain dependencies, the function of the injector is very simple: create dependency instances, obtain dependency instances, and manage dependency instances. We can also see it from the source code of the abstract class Injector:

export abstract class Injector {
  // 找不到依赖
  static THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND;
  // NullInjector 是树的顶部
  // 如果你在树中向上走了很远,以至于要在 NullInjector 中寻找服务,那么将收到错误消息,或者对于 @Optional(),返回 null
  static NULL: Injector = new NullInjector();

  // 根据提供的 Token 从 Injector 检索实例
  abstract get<T>(
    token: Type<T> | AbstractType<T> | InjectionToken<T>,
    notFoundValue?: T,
    flags?: InjectFlags
  ): T;

  // 创建一个新的 Injector 实例,该实例提供一个或多个依赖项
  static create(options: {
    providers: StaticProvider[];
    parent?: Injector;
    name?: string;
  }): Injector;

  // ɵɵdefineInjectable 用于构造一个 InjectableDef
  // 它定义 DI 系统将如何构造 Token,并且在哪些 Injector 中可用
  static ɵprov = ɵɵdefineInjectable({
    token: Injector,
    providedIn: "any" as any,
    // ɵɵinject 生成的指令:从当前活动的 Injector 注入 Token
    factory: () => ɵɵinject(INJECTOR),
  });

  static __NG_ELEMENT_ID__ = InjectorMarkers.Injector;
}

In other words, we can add the dependency instances that need to be shared to the injector and query and retrieve them through Token Injector to obtain the corresponding dependency instance.

It should be noted that the injector in Angular is hierarchical, so the process of finding dependencies is also a process of traversing the injector tree upwards.

This is because in Angular, applications are organized in modules. For details, please refer to 5. Modular Organization. Generally speaking, the DOM of the page is a tree structure with html as the root node. Based on this, the components and modules in the Angular application are also in a tree structure accompanying it.

The injector serves components and modules, and is also mounted in a tree structure of modules and organizations. Therefore, Injector is also divided into module and component levels, which can provide specific instances of dependencies for components and modules respectively. Injectors are inheritable, which means that if the specified injector cannot resolve a dependency, it will ask the parent injector to resolve it. We can also see from the above code to create the injector:

// 创建一个新的 Injector 实例,可传入 parent 父注入器
static create(options: {providers: StaticProvider[], parent?: Injector, name?: string}): Injector;

在某个注入器的范围内,服务是单例的。也就是说,在指定的注入器中最多只有某个服务的最多一个实例。如果不希望在所有地方都使用该服务的同一个实例,则可以通过注册多个注入器、并按照需要关联到组件和模块中的方式,来按需共享某个服务依赖的实例。

我们可以看到创建一个新的Injector实例时,传入的参数包括Provider,这是因为Injector不会直接创建依赖,而是通过Provider来完成的。每个注入器会维护一个提供者的列表,并根据组件或其它服务的需要,用它们来提供服务的实例。

Provider 提供者

Provider 提供者用来告诉注入器应该如何获取或创建依赖,要想让注入器能够创建服务(或提供其它类型的依赖),必须使用某个提供者配置好注入器。

一个提供者对象定义了如何获取与 DI 令牌(token) 相关联的可注入依赖,而注入器会使用这个提供者来创建它所依赖的那些类的实例。

关于 DI 令牌:

  • 当使用提供者配置注入器时,就会把提供者和一个 DI 令牌关联起来;
  • 注入器维护一个内部令牌-提供者的映射表,当请求一个依赖项时就会引用它,令牌就是这个映射表的键。

提供者的类型很多,从官方文档中可以阅读它们的具体定义:

export type Provider =
  | TypeProvider
  | ValueProvider
  | ClassProvider
  | ConstructorProvider
  | ExistingProvider
  | FactoryProvider
  | any[];

提供者的解析过程如下:

function resolveReflectiveFactory(
  provider: NormalizedProvider
): ResolvedReflectiveFactory {
  let factoryFn: Function;
  let resolvedDeps: ReflectiveDependency[];
  if (provider.useClass) {
    // 使用类来提供依赖
    const useClass = resolveForwardRef(provider.useClass);
    factoryFn = reflector.factory(useClass);
    resolvedDeps = _dependenciesFor(useClass);
  } else if (provider.useExisting) {
    // 使用已有依赖
    factoryFn = (aliasInstance: any) => aliasInstance;
    // 从根据 token 获取具体的依赖
    resolvedDeps = [
      ReflectiveDependency.fromKey(ReflectiveKey.get(provider.useExisting)),
    ];
  } else if (provider.useFactory) {
    // 使用工厂方法提供依赖
    factoryFn = provider.useFactory;
    resolvedDeps = constructDependencies(provider.useFactory, provider.deps);
  } else {
    // 使用提供者具体的值作为依赖
    factoryFn = () => provider.useValue;
    resolvedDeps = _EMPTY_LIST;
  }
  //
  return new ResolvedReflectiveFactory(factoryFn, resolvedDeps);
}

根据不同类型的提供者,通过解析之后,得到由注入器 Injector 使用的提供者的内部解析表示形式:

export interface ResolvedReflectiveProvider {
  // 键,包括系统范围内的唯一 id,以及一个 token
  key: ReflectiveKey;
  // 可以返回由键表示的对象的实例的工厂函数
  resolvedFactories: ResolvedReflectiveFactory[];
  // 指示提供者是多提供者,还是常规提供者
  multiProvider: boolean;
}

提供者可以是服务类ClassProvider本身,如果把服务类指定为提供者令牌,那么注入器的默认行为是用new来实例化那个类。

Angular 中的依赖注入服务

在 Angular 中,服务就是一个带有@Injectable装饰器的类,它封装了可以在应用程序中复用的非 UI 逻辑和代码。Angular 把组件和服务分开,是为了增进模块化程度和可复用性。

@Injectable标记一个类,以确保编译器将在注入类时生成必要的元数据(元数据在 Angular 中也是很重要的一部分),以创建类的依赖项。

@Injectable装饰器的类会在编译之后,得到 Angular 可注入对象:

// 根据其 Injectable 元数据,编译 Angular 可注入对象,并对结果进行修补
export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
  // 该编译过程依赖 @angular/compiler
  // 可参考编译器中的 compileFactoryFunction compileInjectable 实现
}

Angular 中可注入对象(InjectableDef)定义 DI 系统将如何构造 token 令牌,以及在哪些注入器(如果有)中可用:

export interface ɵɵInjectableDef<T> {
  // 指定给定类型属于特定注入器,包括 root/platform/any/null 以及特定的 NgModule
  providedIn: InjectorType<any> | "root" | "platform" | "any" | null;
  // 此定义所属的令牌
  token: unknown;
  // 要执行以创建可注入实例的工厂方法
  factory: (t?: Type<any>) => T;
  // 在没有显式注入器的情况下,存储可注入实例的位置
  value: T | undefined;
}

使用@Injectable()providedIn时,优化工具可以进行 Tree-shaking 优化,从而删除应用程序中未使用的服务,以减小捆绑包尺寸。

总结

本文简单介绍了在 Angular 依赖注入体系中比较关键的几个概念,主要包括InjectorProviderInjectable

对于注入器、提供者和可注入服务,我们可以简单地这样理解:

  • 注入器用于创建依赖,会维护一个容器来管理这些依赖,并尽可能地复用它们。

  • 一个注入器中的依赖服务,只有一个实例。

  • 注入器需要使用提供者来管理依赖,并通过 token(DI 令牌)来进行关联。

  • 提供者用于高速注入器应该如何获取或创建依赖。

  • 可注入服务类会根据元数据编译后,得到可注入对象,该对象可用于创建实例。

更多编程相关知识,请访问:编程入门!!

The above is the detailed content of Let's talk about the basic concepts in Angular's ​​dependency injection system. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete