Angular 依赖注入机制实现原理的深入介绍

简介: Angular 依赖注入机制实现原理的深入介绍
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Routes, RouterModule } from '@angular/router';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
export const ROUTER_CONFIG: Routes = [
  { path: '', loadChildren: './home/home.module#HomeModule' },
  { path: 'about', loadChildren: './about/about.module#AboutModule' },
  { path: 'contact', loadChildren: './contact/contact.module#ContactModule' },
];
@NgModule({
  imports: [BrowserModule, HttpModule, RouterModule.forRoot(ROUTER_CONFIG)],
  bootstrap: [AppComponent],
  declarations: [AppComponent],
})
export class AppModule {}

上面的实例代码,定义了一个根组件和一些路由规则,通过这些规则,路由到不同的模块。


Angular 将生成对 VM(虚拟机)友好的代码,以尽可能提高其高性能。


Angular 将为我们的每个模块(module)生成一个注入器 Injector,因此在我们的例子中,它将采用 AppModule(我们的装饰类)并创建一个名为 AppModuleInjector 的注入器。


下面是编译器生成的 AppModule Injector 代码:

import { NgModuleInjector } from '@angular/core/src/linker/ng_module_factory';
import { CommonModule } from '@angular/common/src/common_module';
import { ApplicationModule, _localeFactory } from '@angular/core/src/application_module';
import { BrowserModule, errorHandler } from '@angular/platform-browser/src/browser';
import { RouterModule, ROUTER_FORROOT_GUARD } from '@angular/router/src/router_module';
import { NgLocaleLocalization, NgLocalization } from '@angular/common/src/localization';
import { ApplicationInitStatus, APP_INITIALIZER } from '@angular/core/src/application_init';
import { Testability, TestabilityRegistry } from '@angular/core/src/testability/testability';
import { HttpModule } from '@angular/http/src/http_module';
import { ApplicationRef, ApplicationRef_ } from '@angular/core/src/application_ref';
import { BrowserModule } from '@angular/platform-browser/src/browser';
import { Injector } from '@angular/core/src/di/injector';
import { LOCALE_ID } from '@angular/core/src/i18n/tokens';
import { RouterModule, provideForRootGuard } from '@angular/router/src/router_module';
import { Router } from '@angular/router/src/router';
import { NgZone } from '@angular/core/src/zone/ng_zone';
import { Console } from '@angular/core/src/console';
import { ROUTES } from '@angular/router/src/router_config_loader';
import { ErrorHandler } from '@angular/core/src/error_handler';
import { AppModule } from './app.module';
import { AppComponentNgFactory } from './app.component.ngfactory';
class AppModuleInjector extends NgModuleInjector<AppModule> {
  _CommonModule_0: CommonModule;
  _ApplicationModule_1: ApplicationModule;
  _BrowserModule_2: BrowserModule;
  _ROUTER_FORROOT_GUARD_3: any;
  _RouterModule_4: RouterModule;
  _HttpModule_5: HttpModule;
  _AppModule_6: AppModule;
  _ErrorHandler_7: any;
  _ApplicationInitStatus_8: ApplicationInitStatus;
  _Testability_9: Testability;
  _ApplicationRef__10: ApplicationRef_;
  __ApplicationRef_11: any;
  __ROUTES_12: any[];
  constructor(parent: Injector) {
    super(parent, [AppComponentNgFactory], [AppComponentNgFactory]);  
  }
  get _ApplicationRef_11(): any {
    if (this.__ApplicationRef_11 == null) {
      this.__ApplicationRef_11 = this._ApplicationRef__10;
    }
    return this.__ApplicationRef_11;
  }
  get _ROUTES_12(): any[] {
    if (this.__ROUTES_12 == null) {
      this.__ROUTES_12 = [[
        {
          path: '', loadChildren: './home/home.module#HomeModule'
        },
        {
          path: 'about', loadChildren: './about/about.module#AboutModule'
        },
        {
          path: 'contact', loadChildren: './contact/contact.module#ContactModule'
        }
      ]];
    }
    return this.__ROUTES_12;
  }
  createInternal(): AppModule {
    this._CommonModule_0 = new CommonModule();
    this._ApplicationModule_1 = new ApplicationModule();
    this._BrowserModule_2 = new BrowserModule(this.parent.get(BrowserModule, (null as any)));
    this._ROUTER_FORROOT_GUARD_3 = provideForRootGuard(this.parent.get(Router, (null as any)));
    this._RouterModule_4 = new RouterModule(this._ROUTER_FORROOT_GUARD_3);
    this._HttpModule_5 = new HttpModule();
    this._AppModule_6 = new AppModule();
    this._ErrorHandler_7 = errorHandler();
    this._ApplicationInitStatus_8 = new ApplicationInitStatus(this.parent.get(APP_INITIALIZER, (null as any)));
    this._Testability_9 = new Testability(this.parent.get(NgZone));
    this._ApplicationRef__10 = new ApplicationRef_(
      this.parent.get(NgZone),
      this.parent.get(Console),
      this,
      this._ErrorHandler_7,
      this,
      this._ApplicationInitStatus_8,
      this.parent.get(TestabilityRegistry, (null as any)),
      this._Testability_9
    );
    return this._AppModule_6;
  }
  getInternal(token: any, notFoundResult: any): any {
    if (token === CommonModule) { return this._CommonModule_0; }
    if (token === ApplicationModule) { return this._ApplicationModule_1; }
    if (token === BrowserModule) { return this._BrowserModule_2; }
    if (token === ROUTER_FORROOT_GUARD) { return this._ROUTER_FORROOT_GUARD_3; }
    if (token === RouterModule) { return this._RouterModule_4; }
    if (token === HttpModule) { return this._HttpModule_5; }
    if (token === AppModule) { return this._AppModule_6; }
    if (token === ErrorHandler) { return this._ErrorHandler_7; }
    if (token === ApplicationInitStatus) { return this._ApplicationInitStatus_8; }
    if (token === Testability) { return this._Testability_9; }
    if (token === ApplicationRef_) { return this._ApplicationRef__10; }
    if (token === ApplicationRef) { return this._ApplicationRef_11; }
    if (token === ROUTES) { return this._ROUTES_12; }
    return notFoundResult;
  }
  destroyInternal(): void {
    this._ApplicationRef__10.ngOnDestroy();
  }
}

为了便于阅读,上面所有的 import 代码,已经手动导入更改为命名导入。在实际生成的代码中,每个模块都使用通配符导入以避免命名冲突。

例如, HttpModule 会像这样被导入:

import * as import6 from '@angular/http/src/http_module';

然后使用 import6.HttpModule 而不是 HttpModule 来引用它。

我们会从上述生成代码中学习三个知识点:类的属性、模块导入以及依赖注入机制的工作方式。


AppModuleInjector properties

在 AppModuleInjector 上为每个提供者/依赖项创建属性:

// ...
class AppModuleInjector extends NgModuleInjector<AppModule> {
  _CommonModule_0: CommonModule;
  _ApplicationModule_1: ApplicationModule;
  _BrowserModule_2: BrowserModule;
  // ...
}

这是上面编译输出的一个片段——所以我们将关注定义在类上的三个属性:

  • CommonModule
  • ApplicationModule
  • BrowserModule

我们的模块只声明了 BrowserModule,那么 CommonModule 和 ApplicationModule 是从哪里来的呢? 这些实际上是由 BrowserModule 为我们导出的,所以我们不需要自己导入它们。


模块中每个属性的末尾还附加了一个数字。 就像使用通配符导入一样,这是为了避免提供者之间潜在的命名冲突。


我们可以导入两个使用具有共享名称的服务的模块并且没有递增的数字,它们都将被分配给相同的属性,这可能会导致进一步的错误。

Module imports

编译时,Angular 使用它导入的每个提供程序的直接路径,例如,当我们编写以下代码时:

import { CommonModule } from '@angular/common';

编译后的代码实际上为:

import * as import5 from '@angular/common/src/common_module';

因此,当代码被编译并捆绑在一起时,我们可以利用 tree-shaking 并且只包含我们实际使用的每个模块的部分。

Dependency Injection

每个模块处理自己的依赖注入,如果它没有依赖,则转到父模块,直到找到或未找到它,后者情况下我们会得到一个错误。


重要的是要注意,所有依赖项都使用令牌来唯一标识它们,无论是在注册时还是在查找时。


有两种不同的方式来启动我们的依赖项,要么在 createInternal 中,要么作为属性的 getter。


例如,我们使用 BrowserModule 和 HttpModule,它们是在这里创建的:

class AppModuleInjector extends NgModuleInjector<AppModule> {
  _CommonModule_0: CommonModule;
  _ApplicationModule_1: ApplicationModule;
  _BrowserModule_2: BrowserModule;
  _HttpModule_5: HttpModule;
  _AppModule_6: AppModule;
  createInternal(): AppModule {
    this._CommonModule_0 = new CommonModule();
    this._ApplicationModule_1 = new ApplicationModule();
    this._BrowserModule_2 = new BrowserModule(this.parent.get(BrowserModule, (null as any)));
    this._HttpModule_5 = new HttpModule();
    this._AppModule_6 = new AppModule();
    // ...
    return this._AppModule_6;
  }
}

您可以看到 BrowserModule 的两个导出 - CommonModule 和 ApplicationModule 已创建,以及我们其他导入的模块。 我们的实际模块也被创建(AppModule),因此它可以被其他模块使用。


对于所有其他提供者,它们是在需要时通过类中的 getter 创建的。 这是为了避免在不需要时创建提供程序的实例,同时提高初始渲染性能。


每当我们谈到 Angular 中的注入器时,它指的是从我们的模块中生成(编译)的代码。


当 Angular 查找依赖项(例如我们通过构造函数注入的依赖项)时,它会在模块注入器中查找,如果找不到则向上遍历父模块。 如果它不存在,将会抛出一个错误。


当我们在构造函数中使用类型定义时,Angular 使用这些类型(即类)作为查找依赖项的标记。 然后将该令牌传递给 getInternal 并返回依赖项的实例(如果存在)。再次通过源代码来学习:

class AppModuleInjector extends NgModuleInjector<AppModule> {
  // new BrowserModule(this.parent.get(BrowserModule, (null as any)));
  _BrowserModule_2: BrowserModule;
  // new HttpModule()
  _HttpModule_5: HttpModule;
  // new AppModule()
  _AppModule_6: AppModule;
  getInternal(token: any, notFoundResult: any): any {
    if (token === BrowserModule) {
      return this._BrowserModule_2;
    }
    if (token === HttpModule) {
      return this._HttpModule_5;
    }
    if (token === AppModule) {
      return this._AppModule_6;
    }
    return notFoundResult;
  }
}

因此,在 getInternal 方法中,可以看到 Angular 正在使用简单的 if 语句检查我们的令牌,并将返回提供者的相关属性 - 如果找到的话。


否则,我们将使用返回的 notFoundResult 来拯救 getInternal 方法。 当 Angular 遍历我们的模块以找到所需的依赖项时,这个 notFoundResult 将为空 - 直到它找到依赖项,或者到达根模块但仍然找不到它,此时将会抛出一个错误。


相关文章
|
4月前
|
存储 前端开发 API
浅谈 Angular 应用前端消息显示机制的一个实际需求
浅谈 Angular 应用前端消息显示机制的一个实际需求
|
9天前
|
Java Spring
🔥JSF 与 Spring 强强联手:打造高效、灵活的 Web 应用新标杆!💪 你还不知道吗?
【8月更文挑战第31天】JavaServer Faces(JSF)与 Spring 框架是常用的 Java Web 技术。本文介绍如何整合两者,发挥各自优势,构建高效灵活的 Web 应用。首先通过 `web.xml` 和 `ContextLoaderListener` 配置 Spring 上下文,在 `applicationContext.xml` 定义 Bean。接着使用 `@Autowired` 将 Spring 管理的 Bean 注入到 JSF 管理的 Bean 中。
21 0
|
9天前
|
开发者 容器 Docker
JSF与Docker,引领容器化浪潮!让你的Web应用如虎添翼,轻松应对高并发!
【8月更文挑战第31天】在现代Web应用开发中,JSF框架因其实用性和灵活性被广泛应用。随着云计算及微服务架构的兴起,容器化技术变得日益重要,Docker作为该领域的佼佼者,为JSF应用提供了便捷的部署和管理方案。本文通过基础概念讲解及示例代码展示了如何利用Docker容器化JSF应用,帮助开发者实现高效、便携的应用部署。同时也提醒开发者注意JSF与Docker结合使用时可能遇到的限制,并根据实际情况做出合理选择。
17 0
|
2月前
|
前端开发 容器
前端框架与库 - Angular模块与依赖注入
【7月更文挑战第17天】探索Angular的模块化和依赖注入:模块用于组织组件、服务等,通过`@NgModule`声明。依赖注入简化类间依赖管理,但面临模块重复导入、服务作用域不当和依赖循环等问题。解决策略包括规划模块结构、正确设置服务作用域和使用工厂函数打破循环依赖。遵循最佳实践,构建高效、可维护的Angular应用。
52 17
|
2月前
|
设计模式 JavaScript 测试技术
Angular服务与依赖注入机制详解
【7月更文挑战第17天】Angular的服务与依赖注入机制为构建模块化、可维护和可扩展的应用程序提供了强大的支持。通过合理定义和使用服务,以及利用依赖注入来管理依赖关系,我们可以编写出更加清晰、可维护和可测试的代码。希望本文能帮助你更好地理解和应用Angular的服务与依赖注入机制。
|
10月前
|
设计模式 监控 测试技术
Angular 使用 Constructor Parameters 进行依赖注入的优缺点
Angular 使用 Constructor Parameters 进行依赖注入的优缺点
|
10月前
|
设计模式
Angular 依赖注入领域里 optional constructor parameters 的概念介绍
Angular 依赖注入领域里 optional constructor parameters 的概念介绍
|
10月前
Angular 依赖注入系统里 Injection token PLATFORM_ID 的使用场景
Angular 依赖注入系统里 Injection token PLATFORM_ID 的使用场景
|
10月前
|
移动开发 网络架构 HTML5
Angular 依赖注入系统里 Injection token APP_BASE_HREF 的使用场景
Angular 依赖注入系统里 Injection token APP_BASE_HREF 的使用场景
|
14天前
|
机器学习/深度学习 人工智能 达摩院
第一个Angular应用创建问题之在云开发平台上进行Angular应用的日常环境部署如何解决
第一个Angular应用创建问题之在云开发平台上进行Angular应用的日常环境部署如何解决