关于 Angular 2 Bootstrap 多个应用的说明
这是一篇关于如何在单个 HTML 页面中引导(bootstrap)多个 Angular 应用的注意事项。
引言
这是一篇关于如何在单个 HTML 页面中引导(bootstrap)多个 Angular 应用的注意事项。
背景
Angular 的 网站 提供了一些关于如何引导 Angular 应用的示例,但这些示例只引导了单个 HTML 页面中的一个应用。虽然不是绝对必要,但你可能会发现有必要在单个 HTML 页面中引导多个应用。
- 结构上的原因 - 在 ASP.NET MVC 应用中,通常会将所有页面共用的组件放在布局页面中。你可能希望将这些组件与页面特定的组件分开引导;
- 性能上的原因 - 默认情况下,Angular 的 变更检测 的范围是应用中的整个组件树。如果你知道某些组件中的操作(DOM 事件和回调)对其他组件的状态没有影响,你可能希望将它们单独引导,这样变更检测就会在一个更小的范围内运行。
附加的示例是一个 Visual Studio 2015 Update 3 中的 ASP.NET MVC 应用。如果你想运行它,我强烈建议你看看我 之前的帖子,因为编译和运行一个 Typescript 的 Angular 2 应用并非易事。

该示例在一个 HTML 页面中引导了两个 Angular 应用。
- 每个 Angular 应用的组件位于“app1”和“app2”文件夹中。
- “shared-sub-component”和“math-service”在这两个 Angular 应用之间共享,以演示不同的引导可以共享公共代码。
- 这两个 Angular 应用都将在“Index.cshtml”页面中引导。
以下部分为了完整性而稍显冗长。如果你只对引导多个 Angular 应用感兴趣,可以跳过大部分内容。
共享服务和共享组件
为了演示不同的引导可以共享公共代码,我创建了一个简单的服务和一个简单的组件。
import { Injectable } from '@angular/core';
    
@Injectable()
export class MathService {
    public AddOne(n) {
        return n + 1;
    }
}
“math-service.ts”执行一个简单的数学运算。它将被用于所有组件,以演示 Angular 服务的代码可以被单独引导的组件共享。
import { Component, DoCheck } from '@angular/core';
import { MathService } from '../../services/math-service';
    
@Component({
    selector: 'shared-sub-component',
    template: '<div class="component">' +
    '<h3>Shared Component</h3>' +
    '<div><button (click)="Add()">Click to add Count</button>' +
    ' - {{Count}}</div>' +
    '</div>'
})
    
export class SharedSubComponent implements DoCheck {
    public Count = 0;
    constructor(private math: MathService) { }
    
    public ngDoCheck() {
        console.log('Shared Change detection!')
    }
    
    public Add() {
        this.Count = this.math.AddOne(this.Count);
    }
}
“shared-sub-component.ts”实现了一个 Angular 组件。它将被用作其他组件的子组件,以演示 Angular 组件可以被单独引导的组件共享。
Angular 应用 1
在本篇注意事项中,我将在同一网页上引导两个 Angular 应用。第一个应用实现在“app1”文件夹中。

“component-1.ts”实现了该应用唯一的组件。
import { Component, DoCheck } from '@angular/core';
import { MathService } from '../../services/math-service';
    
@Component({
    selector: 'component-1',
    template: '<div class="component">' +
    '<h3>Component-1</h3>' +
    '<div><button (click)="Add()">Click to add Count</button>' +
    ' - {{Count}}</div>' +
    '<shared-sub-component></shared-sub-component>' +
    '</div>'
})
    
export class Component1 implements DoCheck {
    public Count = 0;
    constructor(private math: MathService) { }
    
    public ngDoCheck() {
        console.log('component-1 Change detection!')
    }
    
    public Add() {
        this.Count = this.math.AddOne(this.Count);
    }
}
“component-1”组件使用“MathService”进行简单的数学运算。它还将“shared-sub-component”用作子组件。“component-1”被打包在“app.module.ts”文件中。
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Component1 } from './components/component-1';
import { SharedSubComponent } from '../common/components/shared-sub-component';
    
import { MathService } from '../services/math-service';
    
@NgModule({
    imports: [BrowserModule],
    providers: [
        MathService
    ],
    declarations: [
        Component1,
        SharedSubComponent
    ],
    bootstrap: [
        Component1
    ]
})
    
export class AppModule { }
“AppModule”将通过“app1.ts”文件引导。
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
    
try { enableProdMode(); } catch(e) { }
const platform = platformBrowserDynamic();
    
platform.bootstrapModule(AppModule);
Angular 应用 2

第二个 Angular 应用实现在“app2”文件夹中。它与第一个应用 virtually 相同。你不需要花时间去查看它。列出它只是为了完整性。
“component-2.ts”组件。
import { Component, DoCheck } from '@angular/core';
import { MathService } from '../../services/math-service';
    
@Component({
    selector: 'component-2',
    providers: [
        MathService
    ],
    template: '<div class="component">' +
    '<h3>Component-2</h3>' +
    '<div><button (click)="Add()">Click to add Count</button>' +
    ' - {{Count}}</div>' +
    '<shared-sub-component></shared-sub-component>' +
    '</div>'
})
    
export class Component2 implements DoCheck {
    public Count = 0;
    constructor(private math: MathService) { }
    
    public ngDoCheck() {
        console.log('component-2 Change detection!')
    }
    
    public Add() {
        this.Count = this.math.AddOne(this.Count);
    }
}
“app.module.ts”模块。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Component2 } from './components/component-2';
import { SharedSubComponent } from '../common/components/shared-sub-component';
    
@NgModule({
    imports: [BrowserModule],
    declarations: [
        Component2,
        SharedSubComponent
    ],
    bootstrap: [
        Component2
    ]
})
    
export class AppModule { }
“app2.ts”应用。
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
    
try { enableProdMode(); } catch (e) { }
const platform = platformBrowserDynamic();
    
platform.bootstrapModule(AppModule);
“systemjs.config.js”文件
为了在单个网页中引导多个 Angular 应用,我对 Angular 官方网站的“systemjs.config.js”文件做了一些修改。
// http://plnkr.co/edit/aZqdJe3OZ8K2odHioWkB?p=info
    
let ng2_getstarter = function (option) {
    let configuration = {
        paths: { 'npm:': option.npm },
        map: {
            app: option.appPath,
            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
            '@angular/platform-browser':
                'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser-dynamic':
                'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
    
            // other libraries
            'rxjs': 'npm:rxjs'
        },
        packages: {
            rxjs: {
                defaultExtension: 'js'
            },
            app: {
                defaultExtension: 'js'
            }
        }
    }
    
    System.config(configuration);
    
    return {
        bootstrap: function (appFile) {
            System.import(appFile).catch(function (err) { console.error(err);});
        }
    };
};
- “ng2_getstarter”函数接受一个 JSON “option”对象,并返回一个用于引导 Angular 应用的对象。
- “option”对象需要提供“npm”文件夹的路径和 Angular 应用根文件夹的路径。
引导应用
有了“systemjs.config.js”文件的帮助,在同一页面中引导多个 Angular 应用非常简单。“Index.cshtml”文件展示了如何使用“ng2_getstarter”函数。
<body>
    <component-1></component-1>
    <component-2></component-2>
</body>
在 HTML 部分,我们要引导“component-1”和“component-2”。“component-1”打包在“app1.ts”文件中,而“component-2”打包在“app2.ts”文件中。
let ng_starter = ng2_getstarter({
    npm: '@Url.Content("~/node_modules/")',
    appPath: '@Url.Content("~/ngApp/")'
});
    
ng_starter.bootstrap('@Url.Content("~/ngApp/app1/app1.js")');
ng_starter.bootstrap('@Url.Content("~/ngApp/app2/app2.js")');
- 通过将“npm”文件夹的路径和 Angular 应用根目录的路径传递给“ng2_getstarter”函数,我们可以获得一个“ng_starter”对象。
- 要引导应用,我们只需在“ng_starter”对象上调用“boostrap()”函数,并将每个 Angular 应用文件的路径传递进去。
“TSC”错误

根据你的 Visual Studio 中安装的 Typescript 编译器,你可能会遇到构建应用的问题。如果你遇到上述错误,很可能是你的 Typescript 编译器版本不匹配。你可以参考我 之前的帖子 来找出解决方案。
构建并加载网页
如果一切顺利,你可以编译并加载网页到浏览器中。

你可以在同一个页面中看到两个 Angular 应用都已成功引导。你可以点击按钮来检查它们是否正常工作。

如果你打开开发者工具查看控制台,你会发现每个应用中的按钮点击只会触发同一应用内的变更检测。如果你不打算让一个应用中的操作更新其他应用的状态,这应该会带来一些性能优势。
关注点
- 这是一篇关于如何在单个 HTML 页面中引导(bootstrap)多个 Angular 应用的注意事项。
- 并非总是需要在同一个页面中引导多个应用,但在某些条件下可能会有一些优势。
- 希望您喜欢我的博文,并希望这篇笔记能以某种方式帮助您。
历史
- 2017/3/5:首次修订




