0%

angular-how does an angular app start

今天从源码的角度来看一下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), // <------- Here
);
}

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>> {
// Note: We need to create the NgZone _before_ we ins
// as instantiating the module creates some providers
// So we create a mini parent injector that just cont
// pass that as parent to the NgModuleFactory.
const ngZone = getNgZone(
options?.ngZone,
getNgZoneOptions({
eventCoalescing: options?.ngZoneEventCoalescing,
runCoalescing: options?.ngZoneRunCoalescing,
}),
);

// Note: Create ngZoneInjector within ngZone.run so that all of the instantiated services are
// created within the Angular zone
// Do not try to replace ngZone.run with ApplicationRef#run because ApplicationRef would then be
// created outside of the Angular zone.
return ngZone.run(() => {...}); // <------- Here
}

注意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)); // <------- Here
} 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); // 1
// ...
this._loadComponent(compRef); // 2
// ...
}

这个方法同样很大,如前,这里省略一些细枝末节,直接展示最重要的代码。

  1. 首先根据bootstrap方法的第一个参数(通常是AppComponent)创建component。
  2. 然后调用内部方法_loadComponent来加载组件。

继续看_loadComponent方法:
packages/core/src/application/application_ref.ts

1
2
3
4
5
private _loadComponent(componentRef: ComponentRef<any>): void {
this.attachView(componentRef.hostView); // 1
this.tick(); // 2
// ...
}
  1. 首先调用attachView方法, 将组件的视图添加到视图树中。
  2. 然后调用tick方法,用来触发变更检测。

tick后续的方法调用如下:
tick -> _tick -> detectChangesInAttachedViews -> detectChangesInViewIfRequired -> detectChangesInternal

再看detectChangesInternal方法:
packages/core/src/render3/instructions/change_detection.ts

未完待续。。。