Angular框架解读--依赖注入的引导过程

开发 前端
本文主要围绕 Angular 中的最大特点——依赖注入,介绍 Angular 依赖注入在体系在应用引导过程中的的设计和实现。

 [[412840]]

作为“为大型前端项目”而设计的前端框架,Angular 其实有许多值得参考和学习的设计,本系列主要用于研究这些设计和功能的实现原理。本文主要围绕 Angular 中的最大特点——依赖注入,介绍 Angular 依赖注入在体系在应用引导过程中的的设计和实现。

多级依赖注入中,介绍了模块注入器和元素注入器两种层次结构的注入器。那么,Angular 在引导过程中,又是如何初始化根模块和入口组件的呢?

Angular 的引导过程

前面我们说到,Angular 应用在浏览器中引导时,会创建浏览器平台,并引导根模块:

  1. platformBrowserDynamic().bootstrapModule(AppModule); 

引导根模块

根模块 AppModule

在 Angular 中,每个应用有至少一个 Angular 模块,根模块就是你用来引导此应用的模块,它通常命名为 AppModule。

当你使用 Angular CLI 命令 ng new 生成一个应用时,其默认的 AppModule 是这样的:

  1. import { BrowserModule } from '@angular/platform-browser'
  2. import { NgModule } from '@angular/core'
  3.  
  4. import { AppComponent } from './app.component'
  5.  
  6. @NgModule({ 
  7.   declarations: [ 
  8.     AppComponent 
  9.   ], 
  10.   imports: [ 
  11.     BrowserModule 
  12.   ], 
  13.   providers: [], 
  14.   bootstrap: [AppComponent] 
  15. }) 
  16. export class AppModule { } 

引导根模块的过程

我们来看看平台层引导根模块的过程中都做了些什么:

  1. @Injectable() 
  2. export class PlatformRef { 
  3.   ... 
  4.  
  5.   bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions): 
  6.       Promise<NgModuleRef<M>> { 
  7.     // 由于实例化模块时,会需要创建一些提供者,所以这里需要在实例化模块之前创建 NgZone 
  8.     // 因此,这里创建了一个仅包含新 NgZone 的微型父注入器,并将其作为父传递给 NgModuleFactory 
  9.     const ngZoneOption = options ? options.ngZone : undefined; 
  10.     const ngZoneEventCoalescing = (options && options.ngZoneEventCoalescing) || false
  11.     const ngZoneRunCoalescing = (options && options.ngZoneRunCoalescing) || false
  12.     const ngZone = getNgZone(ngZoneOption, {ngZoneEventCoalescing, ngZoneRunCoalescing}); 
  13.     const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}]; 
  14.     // ApplicationRef 将在 Angular zone 之外创建 
  15.     return ngZone.run(() => { 
  16.       // 在 ngZone.run 中创建 ngZoneInjector,以便在 Angular zone 中创建所有实例化的服务 
  17.       const ngZoneInjector = Injector.create( 
  18.           {providers: providers, parent: this.injector, name: moduleFactory.moduleType.name}); 
  19.       const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector); 
  20.       const exceptionHandler: ErrorHandler|null = moduleRef.injector.get(ErrorHandler, null); 
  21.       if (!exceptionHandler) { 
  22.         throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?'); 
  23.       } 
  24.       ... 
  25.       return _callAndReportToErrorHandler(exceptionHandler, ngZone!, () => { 
  26.         const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus); 
  27.         initStatus.runInitializers(); 
  28.         return initStatus.donePromise.then(() => { 
  29.           ... 
  30.           // 引导模块 
  31.           this._moduleDoBootstrap(moduleRef); 
  32.           return moduleRef; 
  33.         }); 
  34.       }); 
  35.     }); 
  36.   } 
  37.  
  38.   bootstrapModule<M>( 
  39.       moduleType: Type<M>, 
  40.       compilerOptions: (CompilerOptions&BootstrapOptions)| 
  41.       Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> { 
  42.     const options = optionsReducer({}, compilerOptions); 
  43.     // 编译并创建 @NgModule 的实例 
  44.     return compileNgModuleFactory(this.injector, options, moduleType) 
  45.         .then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options)); 
  46.   } 
  47.  
  48.   private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void { 
  49.     const appRef = moduleRef.injector.get(ApplicationRef) as ApplicationRef; 
  50.     // 引导应用程序 
  51.     if (moduleRef._bootstrapComponents.length > 0) { 
  52.       // 在应用程序的根级别引导新组件 
  53.       moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f)); 
  54.     } else if (moduleRef.instance.ngDoBootstrap) { 
  55.       moduleRef.instance.ngDoBootstrap(appRef); 
  56.     } else { 
  57.       ... 
  58.     } 
  59.     this._modules.push(moduleRef); 
  60.   } 

根模块引导时,除了编译并创建 AppModule 的实例,还会创建 NgZone,关于 NgZone 的请参考。在编译和创建 AppModule 的过程中,便会创建 ApplicationRef ,即 Angular 应用程序。

引导 Angular 应用程序

前面在引导根模块过程中,创建了 Angular 应用程序之后,便会在应用程序的根级别引导新组件:

  1. // 在应用程序的根级别引导新组件 
  2. moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f)); 

我们来看看这个过程会发生什么。

应用程序 ApplicationRef

一个 Angular 应用程序,提供了以下的能力:

  1. @Injectable() 
  2. export class ApplicationRef { 
  3.   // 获取已注册到该应用程序的组件类型的列表 
  4.   public readonly componentTypes: Type<any>[] = []; 
  5.   // 获取已注册到该应用程序的组件的列表 
  6.   public readonly components: ComponentRef<any>[] = []; 
  7.   // 返回一个 Observable,指示应用程序何时稳定或不稳定 
  8.   // 如果在应用程序引导时,引导任何种类的周期性异步任务,则该应用程序将永远不会稳定(例如轮询过程) 
  9.   public readonly isStable!: Observable<boolean>; 
  10.  
  11.   constructor( 
  12.       private _zone: NgZone, private _injector: Injector, private _exceptionHandler: ErrorHandler, 
  13.       private _componentFactoryResolver: ComponentFactoryResolver, 
  14.       private _initStatus: ApplicationInitStatus) { 
  15.         // 创建时,主要进行两件事: 
  16.         // 1. 宏任务结束后,检测视图是否需要更新。 
  17.         // 2. 在 Angular Zone 之外创建对 onStable 的预订,以便在 Angular Zone 之外运行回调。 
  18.   } 
  19.   // 在应用程序的根级别引导新组件 
  20.   bootstrap<C>(componentOrFactory: ComponentFactory<C>|Type<C>, rootSelectorOrNode?: string|any): 
  21.       ComponentRef<C> {} 
  22.   // 调用此方法以显式处理更改检测及其副作用 
  23.   tick(): void {} 
  24.   // 关联视图,以便对其进行脏检查,视图销毁后将自动分离 
  25.   attachView(viewRef: ViewRef): void {} 
  26.   // 再次从脏检查中分离视图 
  27.   detachView(viewRef: ViewRef): void {} 

那么,我们来看看 bootstrap() 过程中,Angular 都做了些什么。

在应用程序的根级别引导根组件

将新的根组件引导到应用程序中时,Angular 将指定的应用程序组件安装到由 componentType 的选择器标识的 DOM 元素上,并引导自动更改检测以完成组件的初始化。

  1. @Injectable() 
  2. export class ApplicationRef { 
  3.   bootstrap<C>(componentOrFactory: ComponentFactory<C>|Type<C>, rootSelectorOrNode?: string|any): 
  4.       ComponentRef<C> { 
  5.     ... 
  6.     // 如果未与其他模块绑定,则创建与当前模块关联的工厂 
  7.     const ngModule = 
  8.         isBoundToModule(componentFactory) ? undefined : this._injector.get(NgModuleRef); 
  9.     const selectorOrNode = rootSelectorOrNode || componentFactory.selector; 
  10.     // 创建组件 
  11.     const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule); 
  12.     const nativeElement = compRef.location.nativeElement; 
  13.     // 创建可测试服务挂钩 
  14.     const testability = compRef.injector.get(Testability, null); 
  15.     const testabilityRegistry = testability && compRef.injector.get(TestabilityRegistry); 
  16.     if (testability && testabilityRegistry) { 
  17.       testabilityRegistry.registerApplication(nativeElement, testability); 
  18.     } 
  19.     // 组件销毁时,销毁关联视图以及相关的服务 
  20.     compRef.onDestroy(() => { 
  21.       this.detachView(compRef.hostView); 
  22.       remove(this.components, compRef); 
  23.       if (testabilityRegistry) { 
  24.         testabilityRegistry.unregisterApplication(nativeElement); 
  25.       } 
  26.     }); 
  27.     // 加载组件,包括关联视图、监听变更等 
  28.     this._loadComponent(compRef); 
  29.     ... 
  30.     return compRef; 
  31.   } 

在创建根组件的过程中,会关联 DOM 元素视图、添加对状态变更的检测机制。

根组件是一个入口组件,Angular CLI 创建的默认应用只有一个组件 AppComponent ,Angular 会在引导过程中把它加载到 DOM 中。

在根组件的创建过程中,通常会根据根组件中引用到的其他组件,触发一系列组件的创建并形成组件树。大多数应用只有一个组件树,并且只从一个根组件开始引导。

创建组件过程

Angular 中创建组件的过程如下:

  1. 当 Angular 创建组件类的新实例时,它会通过查看该组件类的构造函数,来决定该组件依赖哪些服务或其它依赖项。
  2. 当 Angular 发现某个组件依赖某个服务时,它会首先检查是否该注入器中已经有了那个服务的任何现有实例。如果所请求的服务尚不存在,注入器就会使用以前注册的服务提供者来制作一个,并把它加入注入器中,然后把该服务返回给 Angular。
  3. 当所有请求的服务已解析并返回时,Angular 可以用这些服务实例为参数,调用该组件的构造函数。

Angular 会在执行应用时创建注入器,第一个注入器是根注入器,创建于引导过程中。借助注入器继承机制,可以把全应用级的服务注入到这些组件中。

到这里,Angular 分别完成了根模块、根组件和组件树的引导过程,通过编译器则可以将组件和视图渲染到页面上。

总结

在应用程序的引导过程中,Angular 采取了以下步骤来加载我们的第一个视图:

  1. index.html 
  2. Main.ts 

本文我们重点从根模块的引导过程开始,介绍了引导 Angular 应用程序、引导根组件、组件的创建等过程。至于组件树的创建和渲染,则可以参考 编译器 相关的内容。

 

责任编辑:张燕妮 来源: Here. There.
相关推荐

2014-07-08 14:05:48

DaggerAndroid依赖

2009-05-21 16:41:22

GuiceJava依赖注入

2023-10-07 08:35:07

依赖注入Spring

2022-12-29 08:54:53

依赖注入JavaScript

2015-06-17 16:01:30

ASP.NET

2023-06-28 08:16:50

Autofac应用程序

2015-09-02 11:22:36

JavaScript实现思路

2011-05-31 10:00:21

Android Spring 依赖注入

2011-03-29 09:51:58

GuiceIOC

2019-12-20 14:19:47

Linux操作系统引导

2023-07-11 09:14:12

Beanquarkus

2024-05-27 00:13:27

Go语言框架

2017-08-16 16:00:05

PHPcontainer依赖注入

2022-04-11 09:02:18

Swift依赖注

2024-02-04 09:08:00

Autofac容器.NET

2024-01-02 08:22:01

Koin框架项目

2016-12-28 09:30:37

Andriod安卓平台依赖注入

2016-03-21 17:08:54

Java Spring注解区别

2009-10-09 17:51:15

RHEL引导故障

2009-12-10 19:02:30

点赞
收藏

51CTO技术栈公众号