Victor Savkin 大神撰寫了一系列文章詳細介紹如何升級 AngularJS 應用:html
Angular 應用由組件樹構成,每一個組件都擁有一個 注入器
,而且應用的每一個 NgModule 也有注入器。框架在解析組件依賴時,會首先試圖從組件樹獲取,找不到後纔去 NgModule 的注入器中查找依賴。git
藉助 NgUpgrade 咱們能夠在 Angular 應用中啓動一個已經加載好的 AngularJS 應用,一般咱們稱其爲 * 混合應用*( HybridApplication
,不是移動端的那個 Hybrid)。所以咱們便能混合使用兩個框架的組件和 DI 系統。angularjs
最簡單的啓動方式就是在根模塊(通常是 AppModule)的 ngDoBootstrap
方法執行:github
@Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent {} // Access global AngularJS 1.x object const m = angular.module('AngularJsModule', []); m.directive('appRoot', downgradeComponent({component: AppComponent})); @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, UpgradeModule ] }) export class AppModule { constructor(private upgrade: UpgradeModule) {} ngDoBootstrap() { this.upgrade.bootstrap(document.body, ['AngularJsModule']); } }
默認狀況下這是一個很好的方式,確保了升級的組件可以訪問到 AngularJS 原組件。但在懶加載啓動 AngularJS 應用的場景下就行不通了,由於只有用戶導航到指定的路由時才能獲取 AngularJS 相關的引用。shell
UpgradeModule.bootstrap
與 angular.bootstrap
方法簽名相同,若是咱們查看它的實現,會發現本質上也是調用了 angular.bootstrap
,不過作了以下變更:bootstrap
確保 angular.bootstrap
運行在正確的區域瀏覽器
添加額外的模塊配置來確保 Angular 與 AngularJS 能相互引用app
適配 API 使用 Protractor 確保混合應用的可測試性框架
有一點須要注意, @angular/upgrade/static
獲取的是全局的 window.angular
,因此咱們須要在導入升級模塊前導入 AngularJS 框架:ide
import 'angular'; import { UpgradeModule } from '@angular/upgrade/static';
不然會看到 AngularJSv1.xisnotloaded
字樣的報錯。
在懶加載 AngularJS 時,須要咱們手動設置全局 angular
對象:
import * as angular from "angular"; // ... setAngularJSGlobal(angular);
讀者在別的地方可能看到升級使用的是 @angular/upgrade
,那到底用哪一個?
因爲歷史緣由, NgUpgrade
模塊有兩個入口,如上的 @angular/upgrade/static
和 @angular/upgrade
,而咱們應該使用前者,它提供了更完善的報錯而且支持 AOT 模式。
如今咱們知道了如何啓動一個混合應用,如今來看如何橋接 AngularJS 和 Angular 依賴注入系統。
在升級過程當中,升級 服務
(在 Angular 中稱爲 Injectable
,可注入對象)是最重要的工做之一。一般不須要對可注入對象自己作額外的更改,只需在 DI 中正確地設置好它們便可。
假設咱們的 AngularJS 應用有一個以下的可注入對象:
const m = angular.module('AngularJsModule', []); m.value('angularJsInjectable', 'angularJsInjectable-value');
在 Angular 部分,能夠經過 UpgradeModule 提供的 $injector
訪問到它。
const m = angular.module('AngularJsModule', []); m.value('angularJsInjectable', 'angularJsInjectable-value'); function needsAngularJsInjectableFactory($injector) { return `needsAngularJsInjectable got ${$injector.get('angularJsInjectable')}`; } @NgModule({ imports: [ BrowserModule, UpgradeModule ], providers: [ { provide: 'needsAngularJsInjectable', useFactory: needsAngularJsInjectableFactory, deps: ['$injector'] // $injector is provided by UpgradeModule } ] }) export class AppModule { constructor(private upgrade: UpgradeModule) {} ngDoBootstrap() { this.upgrade.bootstrap(document.body, ['AngularJsModule']); console.log(this.upgrade.injector.get('needsAngularJsInjectable')); } }
UpgradeModule 引入了 $injector
(保存了AngularJS 應用的注入器),因此咱們能夠在 AppModule 或它的子模塊中訪問它。
注意在 upgrade.bootstrap
調用以後, $injector
纔會被定義,若是在啓動前訪問則會拋出錯誤。
藉助 UpgradeModule 的 downgradeInjectable
方法,咱們能夠在 AngularJS 應用中訪問到 Angular 的可注入對象:
import { downgradeInjectable, UpgradeModule } from '@angular/upgrade/static'; export class AngularInjectable { get value() { return 'angularInjectable-value'; } } const m = angular.module('AngularJsModule', []); m.factory('angularInjectable', downgradeInjectable(AngularInjectable)); m.factory('needsAngularInjectable', (angularInjectable: AngularInjectable) => `needsAngularInjectable [got ${angularInjectable.value}]`); @NgModule({ imports: [ BrowserModule, UpgradeModule ], providers: [ AngularInjectable ] }) export class AppModule { constructor(private upgrade: UpgradeModule) {} ngDoBootstrap() { this.upgrade.bootstrap(document.body, ['AngularJsModule']); console.log(this.upgrade.$injector.get('needsAngularInjectable')); // 'angularInjectable-value' } }
注意 Angular 的依賴注入運行使用任何類型的 Token 標識可注入對象的依賴,但 AngularJS 只能使用字符串,因此上述代碼中的 m.factory('angularInjectable',downgradeInjectable(AngularInjectable))
會將 AngularInjectable
映射成 angularInjectable
字符串。
NgUpgrade 模塊提供的另外一重要功能是 AngularJS 和 Angular 組件的混合使用。
藉助 downgradeComponent
方法,咱們能夠降級 Angular 組件給 AngularJS 上下文使用:
const m = angular.module('AngularJsModule', []); // The root component of the application, downgraded to AngularJS. @Component({ selector: 'app-root', template: ` AppComponent written in Angular and downgraded to AngularJS' <angularjs-component></angularjs-component> ` }) export class AppComponent {} m.directive('appRoot', downgradeComponent({component: AppComponent}));
全部降級的組件都須要聲明在 Angular 的入口組件列表裏:
@NgModule({ declarations: [ AppComponent, AngularJSComponent, AngularComponent ], // All downgraded components have to be listed here. entryComponents: [ AppComponent, AngularComponent ], imports: [ BrowserModule, UpgradeModule ] }) export class AppModule { constructor(private upgrade: UpgradeModule) {} ngDoBootstrap() { this.upgrade.bootstrap(document.body, ['AngularJsModule']); } }
具體來看, m.directive('appRoot',downgradeComponent({component:AppComponent}));
,將會建立一個選擇器爲 appRoot
的 AngularJS 指令,而該指令將會使用 AppComponent
來渲染它的模板。因爲這層間接關係,咱們須要把 AppComponent
註冊爲入口組件。
<app-root>
元素由 AngularJS 全部,意味着咱們能夠給它應用其它 AngularJS 指令。不過,它的模板,依然是由 Angular 渲染。
downgradeComponent
方法會設置好全部的 AngularJS 綁定關係,即 AppComponent 的輸入輸出。
升級 AngularJS 組件給 Angular 使用則需將它們聲明爲指令,並繼承 UpgradeComponent
:
// An AngularJS component upgraded to Angular. // Note that this is @Directive and not a @Component. @Directive({selector: 'angularjs-component'}) export class AngularJSComponent extends UpgradeComponent { constructor(ref: ElementRef, inj: Injector) { super('angularjsComponent', ref, inj); } } m.component('angularjsComponent', { template: ` angularjsComponent written in AngularJS and upgraded to Angular <angular-component></angular-component> ` });
假設咱們的組件之間有以下輸入輸出配置:
const m = angular.module('AngularJsModule', []); @Component({ selector: 'app-root', template: ` AppComponent written in Angular and downgraded to AngularJS: counter {{counter}} <angularjs-component [counterTimes2]="counter * 2" (multiply)="multiplyCounter($event)"> </angularjs-component> ` }) export class AppComponent { counter = 1; multiplyCounter(n: number): void { this.counter *= n; } } m.directive('appRoot', downgradeComponent({component: AppComponent})); @Directive({selector: 'angularjs-component'}) export class AngularJSComponent extends UpgradeComponent { @Input() counterTimes2: number; @Output() multiply: EventEmitter<number>; constructor(ref: ElementRef, inj: Injector) { super('angularjsComponent', ref, inj); } } m.component('angularjsComponent', { bindings: { counterTimes2: `<`, multiply: '&' }, template: ` angularjsComponent written in AngularJS and upgraded to Angular counterTimes2: {{$ctrl.counterTimes2}} <button ng-click="$ctrl.multiply(2)">Double</button> <angular-component [counter-times-4]="$ctrl.counterTimes2 * 2" (multiply)="$ctrl.multiply($event)"> </angular-component> ` }); @Component({ selector: 'angular-component', template: ` AngularComponent written in Angular and downgraded to AngularJS: counterTimes4: {{counterTimes4}} <button (click)="multiply.next(3)">Triple</button> ` }) export class AngularComponent { @Input() counterTimes4: number; @Output() multiply = new EventEmitter(); } m.directive('angularComponent', downgradeComponent({ component: AngularComponent }));
爲降級組件添加輸入輸出無需額外的配置, downgradeComponent
都爲咱們自動作了,只需在原組件中聲明好便可。
export class AngularComponent { @Input() counterTimes4: number; @Output() multiply = new EventEmitter(); }
而後在 AngularJS 上下文中便可使用綁定關係:
<angular-component [counter-times-4]="$ctrl.counterTimes2 * 2" (multiply)="$ctrl.multiply($event)"> </angular-component>
注意在 Angular 模板中,咱們須要使用中括號和小括號分別標識輸入和輸出。
在升級組件中,咱們須要分別在兩處列出輸入輸出綁定:
@Directive({selector: 'angularjs-component'}) export class AngularJSComponent extends UpgradeComponent { @Input() counterTimes2: number; @Output() multiply: EventEmitter<number>; // Do not create an instance of EventEmitter here constructor(ref: ElementRef, inj: Injector) { super('angularjsComponent', ref, inj); } }
和
m.component('angularjsComponent', { bindings: { counterTimes2: '<', // < corresponds to @Input multiply: '&' // & corresponds to @Output }, template: ` ... ` });
AngularJS 與 Angular 實現雙向綁定的方式徹底不一樣,AngularJS 擁有特殊的雙向綁定機制,而 Angular 則是簡單地利用了輸入/輸出對。NgUpgrade 負責橋接它們。
@Component({ selector: 'app-root', template: ` AppComponent written in Angular and downgraded to AngularJS: counter {{counter}} <angularjs-component [(twoWay)]="counter"> </angularjs-component> ` }) export class AppComponent { } m.directive('appRoot', downgradeComponent({component: AppComponent})); @Directive({selector: 'angularjs-component'}) export class AngularJSComponent extends UpgradeComponent { // We need to declare these two properties. // [(twoWay)]="counter" is the same as [twoWay]="counter" (twoWayChange)="counter=$event" @Input() twoWay: number; @Output() twoWayChange: EventEmitter<number>; constructor(ref: ElementRef, inj: Injector) { super('angularjsComponent', ref, inj); } } m.component('angularjsComponent', { bindings: { twoWay: '=' }, template: ` angularjsComponent written in AngularJS and upgraded to Angular Bound via a two-way binding: <input ng-model="$ctrl.twoWay"> ` });
AngularJS 與 Angular 的變動檢測機制也徹底不一樣。AngularJS 中須要藉助 $scope.apply
來觸發一次變動檢測循環,亦稱爲 digest循環
。在 Angular 中,再也不使用 $scope.apply
,而是依賴於 Zone.js
,每個瀏覽器事件都會觸發一次變動檢測。
因爲混合應用是 Angular 應用,使用 Zone.js,因此咱們再也不須要關注 $scope.apply
。
Angular 還提供了嚴格的機制來確保變動的順序是可預測的,混合應用也保留了這些機制。
AngularJS 和 Angular 都提供了投射內容 DOM 到視圖 DOM 的方式,AngularJS 中稱爲 transclusion
,Angular 中稱爲 reprojection
。
@Component({ selector: 'app-root', template: ` AppComponent written in Angular and downgraded to AngularJS <angularjs-component> Projected from parent </angularjs-component> ` }) export class AppComponent {} m.directive('appRoot', downgradeComponent({component: AppComponent})); @Directive({selector: 'angularjs-component'}) export class AngularJSComponent extends UpgradeComponent { constructor(ref: ElementRef, inj: Injector) { super('angularjsComponent', ref, inj); } } m.component('angularjsComponent', { template: ` angularjsComponent written in AngularJS and upgraded to Angular <ng-transclude></ng-transclude> <angular-component> Projected from parent </angular-component> ` }); @Component({ selector: 'angular-component', template: ` AngularComponent written in Angular and downgraded to AngularJS: <ng-content></ng-content> ` }) export class AngularComponent { } m.directive('angularComponent', downgradeComponent({ component: AngularComponent }));
就像例子中的那樣,一切正常。AngularJS 中使用 <ng-transclude></ng-transclude>
,Angular 中使用 <ng-content></ng-content>
。多插槽投射(Multi-slot reprojection)可能還有些問題,但願不久能修復。
外殼升級(Upgrade Shell)是大多數應用所採用的升級策略。在此策略下,咱們替換或引入 AngularJS 應用的根組件爲 Angular 組件。
假設咱們的 AngularJS 應用以下:
const m = angular.module('AngularJSAppModule', [deps]); m.component(...); m.service(...); angular.bootstrap(document, ['AngularJSAppModule']);
首先移除啓動調用:
const m = angular.module('AngularJSAppModule', [deps]); m.component(...); m.service(...); // angular.bootstrap(document, ['AngularJSAppModule']); - No longer needed
接着定義一個只渲染 ng-view
的根組件:
@Component({ selector: 'app-component', template: `<div class="ng-view"></div>`, }) class AppComponent {}
而後降級註冊到 AngularJS 模塊:
m.directive('appRoot', downgradeComponent({component: AppComponent}));
最後咱們定義一個 Angular 模塊,導入 UpgradeModule
:
@NgModule({ imports: [ BrowserModule, UpgradeModule, ], declarations: [AppComponent], entryComponents: [AppComponent] }) class AppModule { constructor(private upgrade: UpgradeModule) {} ngDoBootstrap() { this.upgrade.bootstrap(document, ['AngularJsAppModule']); } }
這裏咱們使用注入的 UpgradeModule
在 ngDoBootstrap
方法內啓動已加載的 AngularJS 應用。爲了使升級模塊工做正常,只能在 Angular 範圍內執行 upgrade.bootstrap
。
設置完成後,應用啓動執行順序將會是:
Angular 應用啓動
AngularJS 應用啓動
AppComponent 被建立
AngularJS 路由介入並插入視圖到 ng-view
做爲升級應用的第一步,咱們經過此策略,不到5分鐘,便擁有了一個新的 Angular 應用,儘管內部實現都是 AngularJS。
在咱們把應用包裹進上述的外殼後,剩餘部分的升級方式分爲垂直切片(Vertical Slicing)和水平切片(Horizontal Slicin)兩種。
垂直切片的意義在於,儘管一次性重寫整個應用不太實際,但按路由、按功能來重寫一般是可行的。此種場景下,路由頁面多是 AngularJS 寫的,也多是 Angular 寫的。
換句話說,咱們看到的頁面上的全部東西,要麼是 AngularJS 寫的,要麼是 Angular 寫的。
此策略可視化以下:
它的劣勢之一就是 某段時間內不得不爲某些公共組件編寫兩個不一樣的版本,一個使用AngularJS,另外一個使用Angular
。
水平切片與之相反。
先從公共組件開始升級,好比輸入框、日期選擇器等,而後升級使用它們的組件,最後一步步直至升級完根組件。
此種方式的主要特色是不管你打開哪一個頁面,都同時運行着兩個框架。
垂直切片的主要優點是同一時刻咱們的應用只會運行單個框架,意味着代碼更容易 debug,也更容易理解。其次,使用垂直切片可使咱們的升級過程抽象爲單個路由,這對於某些多人維護的大型項目來講尤其重要,由於少了不少相互協做調試的成本。最後,垂直切片容許咱們在導航到遺留路由時才懶加載 NgUpgrade 和 AngularJS,對於應用的體積和加載速度可以有所改善。
水平切片的最大優點是更加細粒度,開發人員能夠升級某個組件並當即發佈到生產環境,而升級路由可能花費數月。
絕大多數的 AngularJS 應用都有使用路由,Angular 應用亦然,那咱們在升級過程當中就不得不一樣時處理兩個路由系統。
URL,具體來講是 window.location
,是一個全局的、可變的狀態,要管理好它並非一件容易的事兒,同時使用不一樣的框架和路由系統時尤甚。升級過程當中多個路由器都會被激活,咱們須要知道怎樣作纔不會出錯。
升級時有兩種 URL 管理設置可選: 單一全部權(SingleOwnership)
和 混合全部權(mixed ownership)
。
假設咱們的混合應用有四個路由。
使用垂直切片升級部分路由後:
在單一全部權設置中,升級到 Angular 的功能由 Angular 路由器管理,其餘遺留的功能由 AngularJS 路由器(或是 UI-router
)管理。也就是說,每個路由都有一個惟一的全部者,要麼是新的 Angular Router,要麼是 AngularJS Router。
在混合全部權設置中,URL 可以同時被 Angular Router 和 AngularJS Router 所管理,其中可能一部分是 AngularJS,而另外一部分是 Angular。
一般可能發生在咱們想要展現某個使用 AngularJS 編寫的對話框,而其它部分已經升級到 Angular 這種場景下。
那到底選哪一個?
儘可能可能的話,咱們應該使用單一全部權設置,它可以使咱們的應用在新老部分之間的過渡更加清晰。同一時刻只升級一個路由,能夠避免某些相關衍生問題。
相鄰出口(SiblingOutlets)
是升級使用多個路由的應用最有用的策略之一,最簡單的實現方式是由 Angular 的 router-outlet
指令和 AngularJS 的 ng-view
指令組成,也就是說有兩個相鄰路由出口,一個給 Angular,另外一個給 AngularJS:
@Component({ selector: 'app-component', template: ` <router-outlet></router-outlet> <div class="ng-view"></div> ` }) class AppComponent { }
在單一全部權設置中,同一時刻只有一個出口是激活的,另外一個爲空。而在混合全部權中,它倆能同時激活。
而後爲已經升級的功能定義 Angular 路由配置,並且咱們要限定路由只處理已升級的功能。主要有兩種實現方式,覆蓋 UrlHandlingStrategy
和空路徑 凹槽路由(SinkRoute)
。
咱們能夠提供一個自定義的 URL 控制策略來告訴 Angular 路由應該處理哪些 URL,對於不符合規則的 URL,它將卸載全部組件並把根路由出口置空。
class CustomHandlingStrategy implements UrlHandlingStrategy { shouldProcessUrl(url) { return url.toString().startsWith("/feature1") || url.toString() === "/"; } extract(url) { return url; } merge(url, whole) { return url; } } @NgModule({ imports: [ BrowserModule, UpgradeModule, RouterModule.forRoot([ { path: '', pathMatch: 'full', component: HomeComponent }, { path: 'feature1/sub1', component: Feature1Sub1Component }, { path: 'feature1/sub2', component: Feature1Sub2Component } ]) ], providers: [ { provide: UrlHandlingStrategy, useClass: CustomHandlingStrategy } ], bootstrap: [AppComponent], declarations: [AppComponent, HomeComponent, Feature1Sub1Component, Feature1Sub2Component] }) class AppModule {}
Angular 處理路由是按照順序來的,若是咱們在配置列表末尾放一個空路徑路由,那匹配不到任何路由後就會匹配任意 URL,此時讓它渲染一個空組件,就實現了一樣的效果。
@Component({selector: 'empty', template: ''}) class EmptyComponent {} @NgModule({ imports: [ BrowserModule, UpgradeModule, RouterModule.forRoot([ { path: '', pathMatch: 'full', component: HomeComponent }, { path: 'feature1/sub1', component: Feature1Sub1Component }, { path: 'feature1/sub2', component: Feature1Sub2Component }, { path: '', component: EmptyComponent } ]) ], bootstrap: [AppComponent], declarations: [AppComponent, HomeComponent, Feature1Sub1Component, Feature1Sub2Component, EmptyComponent] }) class AppModule {}
對於 AngularJS 部分,咱們仍使用 $routeProvider
配置遺留路由,一樣須要設置凹槽路由。
angular.config(($routeProvider) => { $routeProvider .when('/feature2', {template : '<feature2></feature2>'}) .otherwise({template : ''}); });
在 AngularJS 中要實現自定義的 URL 控制策略,能夠訂閱 UI-router 的 $stateChangeStart
事件後調用 preventDefault
來阻止應用導航到已升級的部分。
Angular 路由最棒的一點是支持懶加載,它可讓咱們在渲染應用首屏時所需的靜態資源打包體積儘量小。
上述混合應用的一大特色就是同時給客戶端打包了兩個框架,但這在初始包中實際上是不必的。
咱們能夠設置在只有用戶導航到遺留路由,纔去加載 AngularJS 框架、NgUpgrade 模塊和 AngularJS 相關的業務代碼。
假設咱們的混合應用有四個路由, /angular_a
和 /angular_b
由 Angular 控制,遺留的 AngularJS 應用經過 UI-router 控制着 /angularjs_a
和 /angularjs_b
,入口是 /angular_a
。
@Component({ selector: 'app-root', template: ` <router-outlet></router-outlet> <div ui-view></div> ` }) export class AppComponent {} @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, RouterModule.forRoot([ //... ]) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
根組件配置了相鄰路由出口策略,其中 <divui-view>
只有在 AngularJS 框架加載後纔會激活,在那以前只是一個普通的 DOM 元素。
在 AngularJS 路由配置列表中設置凹槽路由:
export const module = angular.module('AngularJSApp', ['ui.router']); module.config(($locationProvider, $stateProvider) => { //... $stateProvider.state('sink', { url: '/*path', template: '' }); });
Angular 中也定義一個凹槽路由來捕獲不匹配的 URL。
@NgModule({ declarations: [ AppComponent, AngularAComponent, AngularBComponent ], imports: [ BrowserModule, RouterModule.forRoot([ {path: '', redirectTo: 'angular_a', pathMatch: 'full'}, {path: 'angular_a', component: AngularAComponent}, {path: 'angular_b', component: AngularBComponent}, {path: '', loadChildren: './angularjs.module#AngularJSModule'} ]) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Angular 應用會假設沒匹配到的路由交由 AngularJS 處理,因此凹槽路由這裏會去加載 AngularJS 應用的代碼。
AngularJSModule
是 AngularJS 應用的一層簡易 Angular 包裝器。
//angularjs.module.ts import {Component, NgModule} from '@angular/core'; import {RouterModule} from '@angular/router'; import {module} from './angularjsapp'; import {UpgradeModule} from '@angular/upgrade/static'; import {setUpLocationSync} from '@angular/router/upgrade'; @Component({template: ``}) export class EmptyComponent {} @NgModule({ declarations: [ EmptyComponent ], imports: [ UpgradeModule, RouterModule.forChild([ {path: '**', component: EmptyComponent} ]) ] }) export class AngularJSModule { // The constructor is called only once, so we bootstrap the application // only once, when we first navigate to the legacy part of the app. constructor(upgrade: UpgradeModule) { upgrade.bootstrap(document.body, [module.name]); setUpLocationSync(upgrade); } }
爲了使 Angular 路由成功運行,咱們須要渲染一個空組件。而後在模塊的構造函數裏啓動 AngularJS 應用,並設置地址同步,監聽 window.location
變動。
至此整個混合應用搭建完畢,咱們來看下應用具體加載過程。
當用戶打開網頁時,Angular 應用啓動,路由重定向到 /angular_a
,實例化 AngularAComponent 組件,放入 AppComponet 定義的路由出口 <router-outlet>
。
此時咱們還沒有加載 AngularJS、NgUpgrade 和其餘 AngularJS 應用代碼,能夠隨意在 /angular_a
和 /angular_b
路由間切換。
當用戶導航到 /angularjs_a
時,Angular 路由器將會匹配到 {path:'',loadChildren:'./angularjs.module#AngularJSModule'}
路由,執行加載 AngularJSModule
。
這個 chunk 包中的模塊包含了 AngularJS、NgUpgrade 和其餘 AngularJS 應用代碼。
一旦該模塊加載,將會調用 upgrade.bootstrap
方法啓動 AngularJS 應用,觸發 UI-router,匹配到 /angularjs_a
,放入 <divui-view>
。與此同時,Angular 將會把 EmptyComponent
放入 <router-outlet>
。
當用戶從 /angularjs_a
導航到 /angular_a
,UI-router 將會匹配到凹槽路由並將空模板放入 <divui-view>
。 setUpLocationSync
幫助方法將會通知 Angular 路由器 URL 發生變動,那 Angular 路由器就會把 AngularAComponent
放入 <router-outlet>
。
此時 AngularJS 應用仍在運行,並無被卸載。卸載一個真實的 AngularJS 應用幾乎是不可能的,因此咱們把它繼續留在內存中。
當用戶從 /angular_a
導航到 /angular_b
時,UI-router 仍然匹配到凹槽路由,Angular 路由器則更新 <router-outlet>
。
最後,當用戶再導航到 /angularjs_a
時,Angular 路由器匹配到凹槽路由,而正在運行的 UI-router,將會匹配到相應的狀態。此時咱們不必再去加載啓動 AngularJS 應用,由於已經執行過一次。
大功告成,如今只有在用戶導航到受 AngularJS 控制的路由時,咱們纔會加載 AngularJS,這使得首次加載變得很快,但會使用戶初次導航到 /angularjs_a
時加載變慢,咱們能夠開啓預加載來修復這個場景。
//app,module.ts import {PreloadAllModules, RouterModule} from '@angular/router'; @NgModule({ declarations: [ AppComponent, AngularAComponent, AngularBComponent ], imports: [ BrowserModule, RouterModule.forRoot([ {path: '', redirectTo: 'angular_a', pathMatch: 'full'}, {path: 'angular_a', component: AngularAComponent}, {path: 'angular_b', component: AngularBComponent}, {path: '', loadChildren: './angularjs.module#AngularJSModule'} ], { enableTracing: true, preloadingStrategy: PreloadAllModules // ADD THIS! }) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
經過設置預加載策略,當用戶在使用 Angular 應用時,路由器會在背後預加載 AngularJSModule
。
更有趣的是,路由器將會實例化 AngularJSModule
,導致 AngularJS 應用啓動,這意味着整個啓動過程也是發生在背後。
真是一箭雙鵰,咱們既擁有了體積更小的初始化包,隨後又擁有了更快的路由切換。