原文連接: How to manually bootstrap an Angular application
Angular 官方文檔寫到,爲了啓動 Angular 程序,必須在 main.ts
文件裏寫上以下代碼:html
platformBrowserDynamic().bootstrapModule(AppModule);
這行代碼 platformBrowserDynamic()
是爲了構造一個 platform
,Angular 官方文檔對 platform
的定義是(譯者注:爲清晰理解,platform
定義不翻譯):git
the entry point for Angular on a web page. Each page has exactly one platform, and services (such as reflection) which are common to every Angular application running on the page are bound in its scope.
同時,Angular 也有 運行的程序實例(running application instance)的概念,你可使用 ApplicationRef
標記(token)做爲參數注入從而獲取其實例。上文的 platform
定義也隱含了一個 platform
能夠擁有多個 application
對象,而每個 application
對象是經過 bootstrapModule
構造出來的,構造方法就像上文 main.ts
文件中使用的那樣。因此,上文的 main.ts
文件中代碼,首先構造了一個 platform
對象和一個 application
對象。github
當 application
對象被正在構造時,Angular 會去檢查模塊 AppModule
的 bootstrap
屬性,該模塊是用來啓動程序的:web
@NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {}
bootstrap
屬性一般包含用來啓動程序的組件(譯者注:即根組件),Angular 會在 DOM 中查詢並匹配到該啓動組件的選擇器,而後實例化該啓動組件。bootstrap
Angular 啓動過程隱含了你想要哪個組件去啓動程序,可是若是啓動程序的組件是在運行時才被定義的該怎麼辦呢?當你得到該組件時,又該如何啓動程序呢?事實上這是個很是簡單的過程。api
假設咱們有 A
和 B
兩個組件,將編碼決定運行時使用哪個組件來啓動程序,首先讓咱們定義這兩個組件吧:數組
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
中註冊這兩個組件:服務器
@NgModule({ imports: [BrowserModule], declarations: [AComponent, BComponent], entryComponents: [AComponent, BComponent] }) export class AppModule {}
注意,這裏由於咱們得自定義啓動程序,從而沒有在 bootstrap
屬性而是 entryComponents
屬性中註冊這兩個組件,而且經過在 entryComponents
註冊組件,Angular 編譯器(譯者注:Angular 提供了 @angular/compiler
包用來編譯咱們寫的 angular 代碼,同時還提供了 @angular/compiler-cli
CLI 工具)會爲這兩個組件建立工廠類(譯者注:Angular Compiler 在編譯每個組件時,會首先把該組件類轉換爲對應的組件工廠類,即 a.component.ts 被編譯爲 a.component.ngfactory.ts)。由於 Angular 會自動把在 bootstrap
屬性中註冊的組件自動加入入口組件列表,因此一般不須要把根組件註冊到 entryComponents
屬性中。(譯者注:即在 bootstrap
屬性中註冊的組件不須要在 entryComponents
中重複註冊)。app
因爲咱們不知道 A
仍是 B
組件會被使用,因此無法在 index.html
中指定選擇器,因此 index.html
看起來只能這麼寫(譯者注:咱們不知道服務端返回的是 A
仍是 B
組件信息):ide
<body> <h1 id="status"> Loading AppComponent content here ... </h1> </body>
若是此時運行程序會有以下錯誤:
The module AppModule was bootstrapped, but it does not declare 「@NgModule.bootstrap」 components nor a 「ngDoBootstrap」 method. Please define one of these
錯誤信息告訴咱們, Angular 在向抱怨咱們沒有指定具體使用哪個組件來啓動程序,可是咱們的確不能提早知道(譯者注:咱們不知道服務端什麼時候返回什麼)。等會兒咱們得手動在 AppModule
類中添加 ngDoBootstrap
方法來啓動程序:
export class AppModule { ngDoBootstrap(app) { } }
Angular 會把 ApplicationRef
做爲參數傳給 ngDoBootstrap
(譯者注:參考 Angular 源碼中這一行),等會準備啓動程序時,使用 ApplicationRef
的 bootstrap
方法初始化根組件。
讓咱們寫一個自定義方法 bootstrapRootComponent
來啓動根組件:
// 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` //(譯者注:改變 id 爲 #status 的內容) const statusElement = document.querySelector('#status'); statusElement.textContent = 'Loaded'; // create DOM element for the component being bootstrapped // and add it to the DOM // (譯者注:建立一個 DOM 元素) const componentElement = document.createElement(name); document.body.appendChild(componentElement); // bootstrap the application with the selected component const component = options[name]; app.bootstrap(component); // (譯者注:使用 bootstrap() 方法啓動組件) }
傳入該方法的參數是 ApplicationRef
和啓動組件的名稱,同時定義變量 options
來映射全部可能的啓動組件,並以組件選擇器做爲 key,當咱們從服務器中獲取所須要信息後,再根據該信息查詢是哪個組件類。
先構建一個 fetch
方法來模擬 HTTP
請求,該請求會在 2 秒後返回 B
組件選擇器即 b-comp
字符串:
function fetch(url) { return new Promise((resolve) => { setTimeout(() => { resolve('b-comp'); }, 2000); }); }
如今咱們擁有 bootstrap
方法來啓動組件,在 AppModule
模塊的 ngDoBootstrap
方法中使用該啓動方法吧:
export class AppModule { ngDoBootstrap(app) { fetch('url/to/fetch/component/name') .then((name)=>{ this.bootstrapRootComponent(app, name)}); } }
這裏我作了個 stackblitz demo 來驗證該解決方法。(譯者注:譯者把該做者 demo 中 angular 版本升級到最新版本 5.2.9,能夠查看 angular-bootstrap-process,2 秒後會根據服務端返回信息自定義啓動 application
)
固然能夠,你僅僅須要預編譯全部組件,並使用組件的工廠類來啓動程序:
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
屬性中註冊組件,由於咱們已經有了組件的工廠類了,不必再經過 Angular Compiler 去編譯組件得到組件工廠類了。(譯者注:components.ngfactory.ts
是由 Angular AOT Compiler 生成的,最新 Angular 版本 在 CLI 裏隱藏了該信息,在內存裏臨時生成 xxx.factory.ts 文件,不像以前版本能夠經過指令物理生成這中間臨時文件,保存在硬盤裏。)