[譯] 如何手動啓動 Angular 程序

原文連接: 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 會去檢查模塊 AppModulebootstrap 屬性,該模塊是用來啓動程序的:web

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

bootstrap 屬性一般包含用來啓動程序的組件(譯者注:即根組件),Angular 會在 DOM 中查詢並匹配到該啓動組件的選擇器,而後實例化該啓動組件。bootstrap

Angular 啓動過程隱含了你想要哪個組件去啓動程序,可是若是啓動程序的組件是在運行時才被定義的該怎麼辦呢?當你得到該組件時,又該如何啓動程序呢?事實上這是個很是簡單的過程。api

NgDoBootstrap

假設咱們有 AB 兩個組件,將編碼決定運行時使用哪個組件來啓動程序,首先讓咱們定義這兩個組件吧:數組

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 源碼中這一行),等會準備啓動程序時,使用 ApplicationRefbootstrap 方法初始化根組件。

讓咱們寫一個自定義方法 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

在 AOT 中能工做麼?

固然能夠,你僅僅須要預編譯全部組件,並使用組件的工廠類來啓動程序:

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 文件,不像以前版本能夠經過指令物理生成這中間臨時文件,保存在硬盤裏。)

相關文章
相關標籤/搜索