作为“为大型前端项目”而设计的前端框架,Angular 其实有许多值得参考和学习的设计,本系列主要用于研究这些设计和功能的实现原理。本文主要围绕 Angular 中的最大特点——依赖注入,介绍 Angular 依赖注入在体系在应用引导过程中的的设计和实现。
多级依赖注入中,介绍了模块注入器和元素注入器两种层次结构的注入器。那么,Angular 在引导过程中,又是如何初始化根模块和入口组件的呢?
Angular 的引导过程
前面我们说到,Angular 应用在浏览器中引导时,会创建浏览器平台,并引导根模块:
1
| platformBrowserDynamic().bootstrapModule(AppModule);
|
引导根模块
根模块 AppModule
在 Angular 中,每个应用有至少一个 Angular 模块,根模块就是你用来引导此应用的模块,它通常命名为 AppModule。
当你使用 Angular CLI 命令 ng new 生成一个应用时,其默认的 AppModule 是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
|
引导根模块的过程
我们来看看平台层引导根模块的过程中都做了些什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| @Injectable() export class PlatformRef { ...
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions): Promise<NgModuleRef<M>> { const ngZoneOption = options ? options.ngZone : undefined; const ngZoneEventCoalescing = (options && options.ngZoneEventCoalescing) || false; const ngZoneRunCoalescing = (options && options.ngZoneRunCoalescing) || false; const ngZone = getNgZone(ngZoneOption, {ngZoneEventCoalescing, ngZoneRunCoalescing}); const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}]; return ngZone.run(() => { const ngZoneInjector = Injector.create( {providers: providers, parent: this.injector, name: moduleFactory.moduleType.name}); const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector); const exceptionHandler: ErrorHandler|null = moduleRef.injector.get(ErrorHandler, null); if (!exceptionHandler) { throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?'); } ... return _callAndReportToErrorHandler(exceptionHandler, ngZone!, () => { const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus); initStatus.runInitializers(); return initStatus.donePromise.then(() => { ... this._moduleDoBootstrap(moduleRef); return moduleRef; }); }); }); }
bootstrapModule<M>( moduleType: Type<M>, compilerOptions: (CompilerOptions&BootstrapOptions)| Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> { const options = optionsReducer({}, compilerOptions); return compileNgModuleFactory(this.injector, options, moduleType) .then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options)); }
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void { const appRef = moduleRef.injector.get(ApplicationRef) as ApplicationRef; if (moduleRef._bootstrapComponents.length > 0) { moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f)); } else if (moduleRef.instance.ngDoBootstrap) { moduleRef.instance.ngDoBootstrap(appRef); } else { ... } this._modules.push(moduleRef); } }
|
根模块引导时,除了编译并创建 AppModule 的实例,还会创建 NgZone,关于 NgZone 的请参考。在编译和创建 AppModule 的过程中,便会创建ApplicationRef
,即 Angular 应用程序。
引导 Angular 应用程序
前面在引导根模块过程中,创建了 Angular 应用程序之后,便会在应用程序的根级别引导新组件:
1 2
| moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f));
|
我们来看看这个过程会发生什么。
应用程序 ApplicationRef
一个 Angular 应用程序,提供了以下的能力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Injectable() export class ApplicationRef { public readonly componentTypes: Type<any>[] = []; public readonly components: ComponentRef<any>[] = []; public readonly isStable!: Observable<boolean>;
constructor( private _zone: NgZone, private _injector: Injector, private _exceptionHandler: ErrorHandler, private _componentFactoryResolver: ComponentFactoryResolver, private _initStatus: ApplicationInitStatus) { } bootstrap<C>(componentOrFactory: ComponentFactory<C>|Type<C>, rootSelectorOrNode?: string|any): ComponentRef<C> {} tick(): void {} attachView(viewRef: ViewRef): void {} detachView(viewRef: ViewRef): void {} }
|
那么,我们来看看bootstrap()
过程中,Angular 都做了些什么。
在应用程序的根级别引导根组件
将新的根组件引导到应用程序中时,Angular 将指定的应用程序组件安装到由componentType
的选择器标识的 DOM 元素上,并引导自动更改检测以完成组件的初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Injectable() export class ApplicationRef { bootstrap<C>(componentOrFactory: ComponentFactory<C>|Type<C>, rootSelectorOrNode?: string|any): ComponentRef<C> { ... const ngModule = isBoundToModule(componentFactory) ? undefined : this._injector.get(NgModuleRef); const selectorOrNode = rootSelectorOrNode || componentFactory.selector; const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule); const nativeElement = compRef.location.nativeElement; const testability = compRef.injector.get(Testability, null); const testabilityRegistry = testability && compRef.injector.get(TestabilityRegistry); if (testability && testabilityRegistry) { testabilityRegistry.registerApplication(nativeElement, testability); } compRef.onDestroy(() => { this.detachView(compRef.hostView); remove(this.components, compRef); if (testabilityRegistry) { testabilityRegistry.unregisterApplication(nativeElement); } }); this._loadComponent(compRef); ... return compRef; } }
|
在创建根组件的过程中,会关联 DOM 元素视图、添加对状态变更的检测机制。
根组件是一个入口组件,Angular CLI 创建的默认应用只有一个组件AppComponent
,Angular 会在引导过程中把它加载到 DOM 中。
在根组件的创建过程中,通常会根据根组件中引用到的其他组件,触发一系列组件的创建并形成组件树。大多数应用只有一个组件树,并且只从一个根组件开始引导。
创建组件过程
Angular 中创建组件的过程如下(参考服务与依赖注入简介):
- 当 Angular 创建组件类的新实例时,它会通过查看该组件类的构造函数,来决定该组件依赖哪些服务或其它依赖项。
- 当 Angular 发现某个组件依赖某个服务时,它会首先检查是否该注入器中已经有了那个服务的任何现有实例。如果所请求的服务尚不存在,注入器就会使用以前注册的服务提供者来制作一个,并把它加入注入器中,然后把该服务返回给 Angular。
- 当所有请求的服务已解析并返回时,Angular 可以用这些服务实例为参数,调用该组件的构造函数。
Angular 会在执行应用时创建注入器,第一个注入器是根注入器,创建于引导过程中。借助注入器继承机制,可以把全应用级的服务注入到这些组件中。
到这里,Angular 分别完成了根模块、根组件和组件树的引导过程,通过编译器则可以将组件和视图渲染到页面上。
总结
在应用程序的引导过程中,Angular 采取了以下步骤来加载我们的第一个视图:
- 加载
index.html
。
- 加载 Angular、第三方库和应用程序。
- 加载应用程序入口点
Main.ts
。
- 加载根模块。
- 加载根组件。
- 加载模板。
本文我们重点从根模块的引导过程开始,介绍了引导 Angular 应用程序、引导根组件、组件的创建等过程。至于组件树的创建和渲染,则可以参考编译器相关的内容。
参考
查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢
如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢