Angular框架解讀--依賴注入的引導過程
作為“為大型前端項目”而設計的前端框架,Angular 其實有許多值得參考和學習的設計,本系列主要用于研究這些設計和功能的實現原理。本文主要圍繞 Angular 中的最大特點——依賴注入,介紹 Angular 依賴注入在體系在應用引導過程中的的設計和實現。
多級依賴注入中,介紹了模塊注入器和元素注入器兩種層次結構的注入器。那么,Angular 在引導過程中,又是如何初始化根模塊和入口組件的呢?
Angular 的引導過程
前面我們說到,Angular 應用在瀏覽器中引導時,會創建瀏覽器平臺,并引導根模塊:
- platformBrowserDynamic().bootstrapModule(AppModule);
引導根模塊
根模塊 AppModule
在 Angular 中,每個應用有至少一個 Angular 模塊,根模塊就是你用來引導此應用的模塊,它通常命名為 AppModule。
當你使用 Angular CLI 命令 ng new 生成一個應用時,其默認的 AppModule 是這樣的:
- 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 { }
引導根模塊的過程
我們來看看平臺層引導根模塊的過程中都做了些什么:
- @Injectable()
- export class PlatformRef {
- ...
- bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions):
- Promise<NgModuleRef<M>> {
- // 由于實例化模塊時,會需要創建一些提供者,所以這里需要在實例化模塊之前創建 NgZone
- // 因此,這里創建了一個僅包含新 NgZone 的微型父注入器,并將其作為父傳遞給 NgModuleFactory
- 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}];
- // ApplicationRef 將在 Angular zone 之外創建
- return ngZone.run(() => {
- // 在 ngZone.run 中創建 ngZoneInjector,以便在 Angular zone 中創建所有實例化的服務
- 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);
- // 編譯并創建 @NgModule 的實例
- 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 應用程序之后,便會在應用程序的根級別引導新組件:
- // 在應用程序的根級別引導新組件
- moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f));
我們來看看這個過程會發生什么。
應用程序 ApplicationRef
一個 Angular 應用程序,提供了以下的能力:
- @Injectable()
- export class ApplicationRef {
- // 獲取已注冊到該應用程序的組件類型的列表
- public readonly componentTypes: Type<any>[] = [];
- // 獲取已注冊到該應用程序的組件的列表
- public readonly components: ComponentRef<any>[] = [];
- // 返回一個 Observable,指示應用程序何時穩定或不穩定
- // 如果在應用程序引導時,引導任何種類的周期性異步任務,則該應用程序將永遠不會穩定(例如輪詢過程)
- public readonly isStable!: Observable<boolean>;
- constructor(
- private _zone: NgZone, private _injector: Injector, private _exceptionHandler: ErrorHandler,
- private _componentFactoryResolver: ComponentFactoryResolver,
- private _initStatus: ApplicationInitStatus) {
- // 創建時,主要進行兩件事:
- // 1. 宏任務結束后,檢測視圖是否需要更新。
- // 2. 在 Angular Zone 之外創建對 onStable 的預訂,以便在 Angular Zone 之外運行回調。
- }
- // 在應用程序的根級別引導新組件
- 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 元素上,并引導自動更改檢測以完成組件的初始化。
- @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
- Main.ts
本文我們重點從根模塊的引導過程開始,介紹了引導 Angular 應用程序、引導根組件、組件的創建等過程。至于組件樹的創建和渲染,則可以參考 編譯器 相關的內容。