如何手动引导 Angular 应用程序

Angular 官方文档指出,要引导应用程序,您必须将以下内容放入文件中 main.ts:

1. Angular 应用启动过程

1
platformBrowserDynamic().bootstrapModule(AppModule);

声明的第一部分 platformBrowserDynamic()创建了一个平台。Angular 文档将该平台描述为:

网页上 Angular 的入口点。每个页面都只有一个平台,并且该页面上运行的每个 Angular 应用程序所共有的服务(例如反射)都绑定在其范围内。

Angular 还有一个running application instance的概念,您通常可以使用令牌注入该实例 ApplicationRef。一个平台上可能有许多应用程序。每个应用程序都是从基于 Module 定义使用 bootstrapModule 创建的。正如示例中 main.ts 所做的一样。因此,文档中显示的语句首先创建一个平台,然后创建应用程序实例。

创建应用程序时,Angular 会检查模块( 例如: AppModule)的 bootstrap 属性用于引导应用程序的:

1
2
3
4
5
6
7
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}

该属性通常引用您想要用来引导应用程序的组件。然后 Angular 在 DOM 中找到引导组件的选择器元素并初始化该组件。

上述过程意味着您知道要使用哪个组件来引导应用程序。但想象一下这样一种情况:引导应用程序的组件是由服务器在运行时定义的。当您获得此信息后,如何引导应用程序?

2. NgDoBootstrap

假设我们有两个组件 A 组件 和 B 组件。我们将在运行时决定应用程序中应使用哪一个组件。让我们定义这两个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Component } from "@angular/core";

@Component({
selector: "a-comp",
template: `<span>I am A component</span>`,
})
export class AComponent {}

@Component({
selector: "b-comp",
template: `<span>I am B component</span>`,
})
export class BComponent {}

我们将它们注册到 AppModule 中:

1
2
3
4
5
6
@NgModule({
imports: [BrowserModule],
declarations: [AComponent, BComponent],
entryComponents: [AComponent, BComponent],
})
export class AppModule {}

这里需要强调的一点是我们不将它们注册在 bootstrap 属性,因为我们将手动引导它们。另外,我们应该在 entryComponents 中注册它们,因为我们希望编译器为它们创建工厂。Angular 会自动将入口组件属性中指定的所有组件添加到 bootstrap,这就是为什么您通常不将根组件添加到 entryComponents.

另外,由于我们不知道是否会使用 A 或 B 组件,因此我们没有在 index.html 中指定选择器,所以现在 index.html 看起来像这样:

1
2
3
<body>
<h1 id="status">Loading AppComponent content here ...</h1>
</body>

现在,如果您现在运行该应用程序,您将收到以下错误:

1
The module AppModule was bootstrapped, but it does not declare “@NgModule.bootstrap” components nor a “ngDoBootstrap” method. Please define one of these

Angular 通过错误信息告诉我们没有指定应该使用什么组件来进行引导。而且我们事先并不知道。稍后我们将手动引导应用程序,为此我们需要将 ngDoBoostrap 方法添加到 AppModule 中:

1
2
3
export class AppModule {
ngDoBootstrap(app) {}
}

Angular 将以 ApplicationRef 的形式将对正在运行的应用程序的引用传递给 ngDoBootstrap 方法。稍后,当我们准备好引导应用程序时,我们将使用 ApplicationRef 的 bootstrap 方法来初始化根组件。

让我们定义一个自定义方法,bootstrapRootComponent 该方法将负责在根组件可用时引导它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app - reference to the running application (ApplicationRef)
// name - name (selector) of the component to bootstrap
function bootstrapRootComponent(app, name) {
// define the possible bootstrap components
// with their selectors (html host elements)
const options = {
"a-comp": AComponent,
"b-comp": BComponent,
};
// obtain reference to the DOM element that shows status
// and change the status to `Loaded`
const statusElement = document.querySelector("#status");
statusElement.textContent = "Loaded";
// create DOM element for the component being bootstrapped
// and add it to the DOM
const componentElement = document.createElement(name);
document.body.appendChild(componentElement);
// bootstrap the application with the selected component
const component = options[name];
app.bootstrap(component);
}

我还创建了一个模拟 fetch 函数,它模拟向服务器发送请求,并返回 b-comp 的选择器:

1
2
3
4
5
6
7
function fetch(url) {
return new Promise((resolve) => {
setTimeout(() => {
resolve("b-comp");
}, 2000);
});
}

现在我们有了 bootstrap 引导根组件的函数,让我们在 ngDoBootstrap 模块的方法中使用它:

1
2
3
4
5
6
7
8

export class AppModule {
ngDoBootstrap(app) {
fetch('url/to/fetch/component/name')
.then((name)=>{ this.bootstrapRootComponent(app, name)});
}
}

就是这样。这是演示解决方案的stackblitz 示例

3. 它可以与 AOT 一起使用吗?

是的,当然可以。您只需预编译所有组件并在引导应用程序时使用工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {
AComponentNgFactory,
BComponentNgFactory,
} from "./components.ngfactory.ts";
@NgModule({
imports: [BrowserModule],
declarations: [AComponent, BComponent],
})
export class AppModule {
ngDoBootstrap(app) {
fetch("url/to/fetch/component/name").then((name) => {
this.bootstrapRootComponent(app, name);
});
}
bootstrapRootComponent(app, name) {
const options = {
"a-comp": AComponentNgFactory,
"b-comp": BComponentNgFactory,
};
}
}

请注意,我们不需要在 entryComponents 中指定组件,因为我们已经有了工厂并且不需要编译它们。

4. Angular 系列文章

最新更新以及更多 Angular 相关文章请访问 Angular 合集 | 鹏叔的技术博客

5. 参考

How to manually bootstrap an Angular application

作者

鹏叔

发布于

2024-03-17

更新于

2024-08-06

许可协议

评论