今天从源码的角度来看一下Angular是如何启动一个应用的。
创建Angular项目 首先,参照这篇文章 创建一个Angular项目
源码分析 关于如何阅读Angular源码,请参考这里如何阅读Angular源码 。
使用VSCode或者WebStorm打开项目,找到src/main.ts文件,这是Angular应用的入口文件。可以看到如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 import { enableProdMode } from '@angular/core' ;import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' ;import { AppModule } from './app/app.module' ;import { environment } from './environments/environment' ;if (environment.production ) { enableProdMode (); } platformBrowserDynamic ().bootstrapModule (AppModule ) .catch (err => console .error (err));
这里调用platformBrowserDynamic().bootstrapModule来启动Angular项目,我们到源码里面搜索bootstrapModule,找到如下代码:
packages/core/src/platform/platform_ref.ts
1 2 3 4 5 6 7 8 9 10 11 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), ); }
bootstrapModule方法内部调用了 bootstrapModuleFactory 方法,我们继续搜索bootstrapModuleFactory,找到如下代码:
packages/core/src/platform/platform_ref.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bootstrapModuleFactory<M>( moduleFactory : NgModuleFactory <M>, options ?: BootstrapOptions , ): Promise <NgModuleRef <M>> { const ngZone = getNgZone ( options?.ngZone , getNgZoneOptions ({ eventCoalescing : options?.ngZoneEventCoalescing , runCoalescing : options?.ngZoneRunCoalescing , }), ); return ngZone.run (() => {...}); }
注意bootstrapModuleFactory这个方法比较长,我们简化一下,只截取其中的关键部分,首先这里创建了一个ngZone,用来做更新检测。然后调用了ngZone.run方法,这个方法是NgZone的方法,用来运行一个函数,并且确保这个函数在Angular的Zone中运行。在return ngZone.run()方法中的最后,又调用了_moduleDoBootstrap。
packages/core/src/platform/platform_ref.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private _moduleDoBootstrap (moduleRef : InternalNgModuleRef <any >): void { const appRef = moduleRef.injector .get (ApplicationRef ); if (moduleRef._bootstrapComponents .length > 0 ) { moduleRef._bootstrapComponents .forEach ((f ) => appRef.bootstrap (f)); } else if (moduleRef.instance .ngDoBootstrap ) { moduleRef.instance .ngDoBootstrap (appRef); } else { throw new RuntimeError ( RuntimeErrorCode .BOOTSTRAP_COMPONENTS_NOT_FOUND , ngDevMode && `The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, ` + `but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` + `Please define one of these.` , ); } this ._modules .push (moduleRef); }
在该方法中,对于每个声明的bootstrap组件,都会调用appRef.bootstrap方法,这个方法是ApplicationRef的方法,用来启动一个组件。通常Angular程序只有一个bootstrap组件,就是AppComponent。 再来看appRef.bootstrap方法:
packages/core/src/application/application_ref.ts
1 2 3 4 5 6 7 8 9 10 11 bootstrap<C>( componentOrFactory : ComponentFactory <C> | Type <C>, rootSelectorOrNode ?: string | any , ): ComponentRef <C> { const selectorOrNode = rootSelectorOrNode || componentFactory.selector ; const compRef = componentFactory.create (Injector .NULL , [], selectorOrNode, ngModule); this ._loadComponent (compRef); }
这个方法同样很大,如前,这里省略一些细枝末节,直接展示最重要的代码。
首先根据bootstrap方法的第一个参数(通常是AppComponent)创建component。
然后调用内部方法_loadComponent来加载组件。
继续看_loadComponent方法:packages/core/src/application/application_ref.ts
1 2 3 4 5 private _loadComponent (componentRef : ComponentRef <any >): void { this .attachView (componentRef.hostView ); this .tick (); }
首先调用attachView方法, 将组件的视图添加到视图树中。
然后调用tick方法,用来触发变更检测。
tick后续的方法调用如下:tick -> _tick -> detectChangesInAttachedViews -> detectChangesInViewIfRequired -> detectChangesInternal
再看detectChangesInternal方法:packages/core/src/render3/instructions/change_detection.ts
未完待续。。。