新聞中心
從 AngularJS 升級(jí)到 Angular
Angular是現(xiàn)在和未來(lái)的 Angular 名稱。

AngularJS是所有 1.x 版本的 Angular 的名稱。
有很多大型 AngularJS 應(yīng)用。在遷移到 Angular 之前,請(qǐng)始終考慮其業(yè)務(wù)案例。該案例的一個(gè)重要部分是遷移的時(shí)間和精力。本指南描述了用于將 AngularJS 項(xiàng)目高效遷移到 Angular 平臺(tái)的內(nèi)置工具,一次一個(gè)。
有些應(yīng)用可能比其它的升級(jí)起來(lái)簡(jiǎn)單,還有一些方法能讓把這項(xiàng)工作變得更簡(jiǎn)單。 即使在正式開(kāi)始升級(jí)過(guò)程之前,可以提前準(zhǔn)備 AngularJS 的程序,讓它向 Angular 看齊。 這些準(zhǔn)備步驟幾乎都是關(guān)于如何讓代碼更加松耦合、更有可維護(hù)性,以及用現(xiàn)代開(kāi)發(fā)工具提高速度的。 這意味著,這種準(zhǔn)備工作不僅能讓最終的升級(jí)變得更簡(jiǎn)單,而且還能提升 AngularJS 程序的質(zhì)量。
成功升級(jí)的關(guān)鍵之一是增量式的實(shí)現(xiàn)它,通過(guò)在同一個(gè)應(yīng)用中一起運(yùn)行這兩個(gè)框架,并且逐個(gè)把 AngularJS 的組件遷移到 Angular 中。 這意味著可以在不必打斷其它業(yè)務(wù)的前提下,升級(jí)更大、更復(fù)雜的應(yīng)用程序,因?yàn)檫@項(xiàng)工作可以多人協(xié)作完成,在一段時(shí)間內(nèi)逐漸鋪開(kāi)。 Angular upgrade 模塊的設(shè)計(jì)目標(biāo)就是讓你漸進(jìn)、無(wú)縫的完成升級(jí)。
準(zhǔn)備
AngularJS 應(yīng)用程序的組織方式有很多種。當(dāng)你想把它們升級(jí)到 Angular 的時(shí)候, 有些做起來(lái)會(huì)比其它的更容易些。即使在開(kāi)始升級(jí)之前,也有一些關(guān)鍵的技術(shù)和模式可以讓你將來(lái)升級(jí)時(shí)更輕松。
遵循 AngularJS 風(fēng)格指南
AngularJS 風(fēng)格指南收集了一些已證明能寫(xiě)出干凈且可維護(hù)的 AngularJS 程序的模式與實(shí)踐。 它包含了很多關(guān)于如何書(shū)寫(xiě)和組織 AngularJS 代碼的有價(jià)值信息,同樣重要的是,不應(yīng)該采用的書(shū)寫(xiě)和組織 AngularJS 代碼的方式。
Angular 是一個(gè)基于 AngularJS 中最好的部分構(gòu)思出來(lái)的版本。在這種意義上,它的目標(biāo)和 AngularJS 風(fēng)格指南是一樣的: 保留 AngularJS 中好的部分,去掉壞的部分。當(dāng)然,Angular 還做了更多。 說(shuō)這些的意思是:遵循這個(gè)風(fēng)格指南可以讓你寫(xiě)出更接近 Angular 程序的 AngularJS 程序。
有一些特別的規(guī)則可以讓使用 Angular 的 ?upgrade/static? 模塊進(jìn)行增量升級(jí)變得更簡(jiǎn)單:
- 單一規(guī)則 規(guī)定每個(gè)文件應(yīng)該只放一個(gè)組件。這不僅讓組件更容易瀏覽和查找,而且還讓你能逐個(gè)遷移它們的語(yǔ)言和框架。 在這個(gè)范例程序中,每個(gè)控制器、工廠和過(guò)濾器都位于各自的源文件中。
- 按特性分目錄的結(jié)構(gòu)和模塊化規(guī)則在較高的抽象層定義了一些相似的原則:應(yīng)用程序中的不同部分應(yīng)該被分到不同的目錄和 NgModule 中。
如果應(yīng)用程序能用這種方式把每個(gè)特性分到一個(gè)獨(dú)立目錄中,它也就能每次遷移一個(gè)特性。 對(duì)于那些還沒(méi)有這么做的程序,強(qiáng)烈建議把應(yīng)用這條規(guī)則作為準(zhǔn)備步驟。而且這也不僅僅對(duì)升級(jí)有價(jià)值, 它還是一個(gè)通用的規(guī)則,可以讓你的程序更“堅(jiān)實(shí)”。
使用模塊加載器
當(dāng)你把應(yīng)用代碼分解到每個(gè)文件中只放一個(gè)組件的粒度后,通常會(huì)得到一個(gè)由大量相對(duì)較小的文件組成的項(xiàng)目結(jié)構(gòu)。 這比組織成少量大文件要整潔得多,但如果你不得不通過(guò) ?? 標(biāo)簽在 HTML 頁(yè)面中加載所有這些文件,那就不好玩了。 尤其是當(dāng)你不得不自己按正確的順序維護(hù)這些標(biāo)簽時(shí)更是如此,就要開(kāi)始使用模塊加載器了。
使用模塊加載器,比如SystemJS、 Webpack或Browserify, 可以讓你在程序中使用 TypeScript 或 ES2015 語(yǔ)言內(nèi)置的模塊系統(tǒng)。 你可以使用 ?import ?和 ?export ?特性來(lái)明確指定哪些代碼應(yīng)該以及將會(huì)被在程序的不同部分之間共享。 對(duì)于 ES5 程序來(lái)說(shuō),可以改用 CommonJS 風(fēng)格的 ?require ?和 ?module.exports? 特性代替。 無(wú)是論哪種情況,模塊加載器都會(huì)按正確的順序加載程序中用到的所有代碼。
當(dāng)要把應(yīng)用程序投入生產(chǎn)環(huán)境時(shí),模塊加載器也會(huì)讓你把所有這些文件打成完整的產(chǎn)品包變得容易一些。
遷移到 TypeScript
Angular 升級(jí)計(jì)劃的一部分是引入 TypeScript,即使在開(kāi)始升級(jí)之前,引入 TypeScript 編譯器也是有意義的。 這意味著等真正升級(jí)的時(shí)候需要學(xué)習(xí)和思考的東西會(huì)更少,并且你可以在 AngularJS 代碼中開(kāi)始使用 TypeScript 的特性。
TypeScript 是 ECMAScript 2015 的超集,而 ES2015 又是 ECMAScript 5 的超集。 這意味著除了安裝一個(gè) TypeScript 編譯器,并把文件名都從 ?*.js? 改成 ?*.ts? 之外,其實(shí)什么都不用做。 當(dāng)然,如果僅僅這樣做也沒(méi)什么大用,也沒(méi)什么有意思的地方。 下面這些額外的步驟可以讓你打起精神:
- 對(duì)那些使用了模塊加載器的程序,TypeScript 的導(dǎo)入和導(dǎo)出語(yǔ)法(實(shí)際上是 ECMAScript 2015 的導(dǎo)入和導(dǎo)出)可以把代碼組織成模塊。
- 可以逐步把類型注解添加到現(xiàn)有函數(shù)和變量上,以固定它們的類型,并獲得其優(yōu)點(diǎn):比如編譯期錯(cuò)誤檢查、更好的支持自動(dòng)完成,以及內(nèi)聯(lián)式文檔等。
- 那些 ES2015 中新增的特性,比如箭頭函數(shù)、?
let?、?const?、默認(rèn)函數(shù)參數(shù)、解構(gòu)賦值等也可以逐漸添加進(jìn)來(lái),讓代碼更有表現(xiàn)力。 - 服務(wù)和控制器可以轉(zhuǎn)成類。這樣它們就能一步步接近 Angular 的服務(wù)和組件類了,也會(huì)讓升級(jí)變得簡(jiǎn)單一點(diǎn)。
使用組件型指令
在 Angular 中,組件是用來(lái)構(gòu)建用戶界面的主要元素。你把 UI 中的不同部分定義成組件,然后在模板中使用這些組件合成出最終的 UI。
你在 AngularJS 中也能這么做。那就是一種定義了自己的模板、控制器和輸入/輸出綁定的指令 —— 跟 Angular 中對(duì)組件的定義是一樣的。 要遷移到 Angular,通過(guò)組件型指令構(gòu)建的應(yīng)用程序會(huì)比直接用 ?ng-controller?、?ng-include? 和作用域繼承等底層特性構(gòu)建的要容易得多。
要與 Angular 兼容,AngularJS 的組件型指令應(yīng)該配置下列屬性:
- ?
restrict: 'E'?。組件通常會(huì)以元素的方式使用。 - ?
scope: {}? - 一個(gè)獨(dú)立作用域。在 Angular 中,組件永遠(yuǎn)是從它們的環(huán)境中被隔離出來(lái)的,在 AngularJS 中也同樣如此。 - ?
bindToController: {}?。組件的輸入和輸出應(yīng)該綁定到控制器,而不是 ?$scope?。 - ?
controller?和 ?controllerAs?。組件要有自己的控制器。 - ?
template?或 ?templateUrl?。組件要有自己的模板。
組件型指令還可能使用下列屬性:
- ?
transclude: true?:如果組件需要從其它地方透?jìng)鲀?nèi)容,就設(shè)置它。 - ?
require?:如果組件需要和父組件的控制器通訊,就設(shè)置它。
組件型指令不能使用下列屬性:
- ?
compile?。Angular 不再支持它。 - ?
replace: true?。Angular 永遠(yuǎn)不會(huì)用組件模板替換一個(gè)組件元素。這個(gè)特性在 AngularJS 中也同樣不建議使用了。 - ?
priority?和 ?terminal?。雖然 AngularJS 的組件可能使用這些,但它們?cè)?nbsp;Angular 中已經(jīng)沒(méi)用了,并且最好不要再寫(xiě)依賴它們的代碼。
AngularJS 中一個(gè)完全向 Angular 架構(gòu)對(duì)齊過(guò)的組件型指令是這樣的:
export function heroDetailDirective() {
return {
restrict: 'E',
scope: {},
bindToController: {
hero: '=',
deleted: '&'
},
template: `
{{$ctrl.hero.name}} details!
{{$ctrl.hero.id}}
`,
controller: function HeroDetailController() {
this.onDelete = () => {
this.deleted({hero: this.hero});
};
},
controllerAs: '$ctrl'
};
}AngularJS 1.5 引入了組件 API,它讓定義指令變得更簡(jiǎn)單了。 為組件型指令使用這個(gè) API 是一個(gè)好主意,因?yàn)椋?/p>
- 它需要更少的樣板代碼。
- 它強(qiáng)制你遵循組件的最佳實(shí)踐,比如 ?
controllerAs?。 - 指令中像 ?
scope?和 ?restrict?這樣的屬性應(yīng)該有良好的默認(rèn)值。
如果使用這個(gè)組件 API 進(jìn)行表示,那么上面看到的組件型指令就變成了這樣:
export const heroDetail = {
bindings: {
hero: '<',
deleted: '&'
},
template: `
{{$ctrl.hero.name}} details!
{{$ctrl.hero.id}}
`,
controller: function HeroDetailController() {
this.onDelete = () => {
this.deleted(this.hero);
};
}
};控制器的生命周期鉤子 ?$onInit()?、?$onDestroy()? 和 ?$onChanges()? 是 AngularJS 1.5 引入的另一些便利特性。 它們都很像Angular 中的等價(jià)物,所以,圍繞它們組織組件生命周期的邏輯在升級(jí)到 Angular 時(shí)會(huì)更容易。
使用 ngUpgrade 升級(jí)
不管要升級(jí)什么,Angular 中的 ?ngUpgrade ?庫(kù)都會(huì)是一個(gè)非常有用的工具 —— 除非是小到?jīng)]功能的應(yīng)用。 借助它,你可以在同一個(gè)應(yīng)用程序中混用并匹配 AngularJS 和 Angular 的組件,并讓它們實(shí)現(xiàn)無(wú)縫的互操作。 這意味著你不用被迫一次性做完所有的升級(jí)工作,因?yàn)樵谡麄€(gè)演進(jìn)過(guò)程中,這兩個(gè)框架可以很自然的和睦相處。
由于 AngularJS 即將停止維護(hù) ,ngUpgrade 現(xiàn)在處于特性開(kāi)發(fā)完畢的狀態(tài)。我們將會(huì)繼續(xù)發(fā)布安全補(bǔ)丁和 BUG 修復(fù),直到 2022-12-31。
ngUpgrade 的工作原理
?ngUpgrade ?提供的主要工具之一被稱為 ?UpgradeModule?。這是一個(gè)服務(wù),它可以啟動(dòng)并管理一個(gè)能同時(shí)支持 Angular 和 AngularJS 的混合式應(yīng)用。
當(dāng)使用 ngUpgrade 時(shí),你實(shí)際上在同時(shí)運(yùn)行 AngularJS 和 Angular。所有 Angular 的代碼運(yùn)行在 Angular 框架中,而 AngularJS 的代碼運(yùn)行在 AngularJS 框架中。所有這些都是真實(shí)的、全功能的框架版本。 沒(méi)有進(jìn)行任何仿真,所以你可以認(rèn)為同時(shí)存在著這兩個(gè)框架的所有特性和自然行為。
所有這些事情的背后,本質(zhì)上是一個(gè)框架中管理的組件和服務(wù)能和來(lái)自另一個(gè)框架的進(jìn)行互操作。 這些主要體現(xiàn)在三個(gè)方面:依賴注入、DOM 和變更檢測(cè)。
依賴注入
無(wú)論是在 AngularJS 中還是在 Angular 中,依賴注入都位于前沿和中心的位置,但在兩個(gè)框架的工作原理上,卻存在著一些關(guān)鍵的不同之處。
| ANGULARJS | ANGULAR |
|---|---|
依賴注入的令牌(Token)永遠(yuǎn)是字符串(譯注:指服務(wù)名稱)。 | 令牌可以有不同的類型。 |
只有一個(gè)注入器。 | 這是一個(gè)樹(shù)狀分層注入器:有一個(gè)根注入器,而且每個(gè)組件也有一個(gè)自己的注入器。 |
就算有這么多不同點(diǎn),也并不妨礙你在依賴注入時(shí)進(jìn)行互操作。?UpgradeModule ?解決了這些差異,并讓它們無(wú)縫的對(duì)接:
- 通過(guò)升級(jí)它們,你就能讓那些在 AngularJS 中能被注入的服務(wù)也可用于 Angular 的代碼中。 在框架之間共享的是服務(wù)的同一個(gè)單例對(duì)象。在 Angular 中,這些外來(lái)服務(wù)總是被放在根注入器中,并可用于所有組件。 它們總是具有字符串令牌 —— 跟它們?cè)?nbsp;AngularJS 中的令牌相同。
- 通過(guò)降級(jí)它們,你也能讓那些在 Angular 中能被注入的服務(wù)在 AngularJS 的代碼中可用。 只有那些來(lái)自 Angular 根注入器的服務(wù)才能被降級(jí)。同樣的,在框架之間共享的是同一個(gè)單例對(duì)象。 當(dāng)你注冊(cè)一個(gè)要降級(jí)的服務(wù)時(shí),要明確指定一個(gè)打算在 AngularJS 中使用的字符串令牌。
組件與 DOM
在混合式應(yīng)用中,同時(shí)存在來(lái)自 AngularJS 和 Angular 中組件和指令的 DOM。 這些組件通過(guò)它們各自框架中的輸入和輸出綁定來(lái)互相通訊,它們由 ?UpgradeModule ?橋接在一起。 它們也能通過(guò)共享被注入的依賴彼此通訊,就像前面所說(shuō)的那樣。
理解混合式應(yīng)用的關(guān)鍵在于,DOM 中的每一個(gè)元素都只能屬于這兩個(gè)框架之一,而另一個(gè)框架則會(huì)忽略它。如果一個(gè)元素屬于 AngularJS,那么 Angular 就會(huì)當(dāng)它不存在,反之亦然。
所以,混合式應(yīng)用總是像 AngularJS 程序那樣啟動(dòng),處理根模板的也是 AngularJS. 然后,當(dāng)這個(gè)應(yīng)用的模板中使用到了 Angular 的組件時(shí),Angular 才開(kāi)始參與。 這個(gè)組件的視圖由 Angular 進(jìn)行管理,而且它還可以使用一系列的 Angular 組件和指令。
更進(jìn)一步說(shuō),你可以按照需要,任意穿插使用這兩個(gè)框架。 使用下面的兩種方式之一,你可以在這兩個(gè)框架之間自由穿梭:
- 通過(guò)使用來(lái)自另一個(gè)框架的組件:AngularJS 的模板中用到了 Angular 的組件,或者 Angular 的模板中使用了 AngularJS 的組件。
- 通過(guò)透?jìng)?transclude)或投影(project)來(lái)自另一個(gè)框架的內(nèi)容。?
UpgradeModule?牽線搭橋,把 AngularJS 的透?jìng)鞲拍詈?nbsp;Angular 的內(nèi)容投影概念關(guān)聯(lián)起來(lái)。
當(dāng)你使用一個(gè)屬于另一個(gè)框架的組件時(shí),就會(huì)發(fā)生一次跨框架邊界的切換。不過(guò),這種切換只發(fā)生在該組件元素的子節(jié)點(diǎn)上。 考慮一個(gè)場(chǎng)景,你從 AngularJS 中使用一個(gè) Angular 組件,就像這樣:
此時(shí),?? 這個(gè) DOM 元素仍然由 AngularJS 管理,因?yàn)樗窃?nbsp;AngularJS 的模板中定義的。 這也意味著你可以往它上面添加別的 AngularJS 指令,卻不能添加 Angular 的指令。 只有在 ?? 組件的模板中才是 Angular 的天下。同樣的規(guī)則也適用于在 Angular 中使用 AngularJS 組件型指令的情況。
變更檢測(cè)
AngularJS 中的變更檢測(cè)全是關(guān)于 ?scope.$apply()? 的。在每個(gè)事件發(fā)生之后,?scope.$apply()? 就會(huì)被調(diào)用。 這或者由框架自動(dòng)調(diào)用,或者在某些情況下由你自己的代碼手動(dòng)調(diào)用。
在 Angular 中,事情有點(diǎn)不一樣。雖然變更檢測(cè)仍然會(huì)在每一個(gè)事件之后發(fā)生,卻不再需要每次調(diào)用 ?scope.$apply()? 了。 這是因?yàn)樗?nbsp;Angular 代碼都運(yùn)行在一個(gè)叫做 ?Angular zone? 的地方。 Angular 總是知道什么時(shí)候代碼執(zhí)行完了,也就知道了它什么時(shí)候應(yīng)該觸發(fā)變更檢測(cè)。代碼本身并不需要調(diào)用 ?scope.$apply()? 或其它類似的東西。
在這種混合式應(yīng)用的案例中,?UpgradeModule ?在 AngularJS 的方法和 Angular 的方法之間建立了橋梁。發(fā)生了什么呢?
- 應(yīng)用中發(fā)生的每件事都運(yùn)行在 Angular 的 zone 里。 無(wú)論事件發(fā)生在 AngularJS 還是 Angular 的代碼中,都是如此。 這個(gè) zone 會(huì)在每個(gè)事件之后觸發(fā) Angular 的變更檢測(cè)。
- ?
UpgradeModule?將在每一次離開(kāi) Angular zone 時(shí)調(diào)用 AngularJS 的 ?$rootScope.$apply()?。這樣也就同樣會(huì)在每個(gè)事件之后觸發(fā) AngularJS 的變更檢測(cè)。
在實(shí)踐中,你不用在自己的代碼中調(diào)用 ?$apply()?,而不用管這段代碼是在 AngularJS 還是 Angular 中。 ?UpgradeModule ?都替你做了。你仍然可以調(diào)用 ?$apply()?,也就是說(shuō)你不必從現(xiàn)有代碼中移除此調(diào)用。 在混合式應(yīng)用中,這些調(diào)用只會(huì)觸發(fā)一次額外的 AngularJS 變更檢測(cè)。
當(dāng)你降級(jí)一個(gè) Angular 組件,然后把它用于 AngularJS 中時(shí),組件的輸入屬性就會(huì)被 AngularJS 的變更檢測(cè)體系監(jiān)視起來(lái)。 當(dāng)那些輸入屬性發(fā)生變化時(shí),組件中相應(yīng)的屬性就會(huì)被設(shè)置。你也能通過(guò)實(shí)現(xiàn)?OnChanges ?接口來(lái)掛鉤到這些更改,就像它未被降級(jí)時(shí)一樣。
相應(yīng)的,當(dāng)你把 AngularJS 的組件升級(jí)給 Angular 使用時(shí),在這個(gè)組件型指令的 ?scope?(或 ?bindToController?)中定義的所有綁定, 都將被掛鉤到 Angular 的變更檢測(cè)體系中。它們將和標(biāo)準(zhǔn)的 Angular 輸入屬性被同等對(duì)待,并當(dāng)它們發(fā)生變化時(shí)設(shè)置回 scope(或控制器)上。
通過(guò) Angular 的 NgModule 來(lái)使用 UpgradeModule
AngularJS 還是 Angular 都有自己的模塊概念,來(lái)幫你把應(yīng)用組織成一些內(nèi)聚的功能塊。
它們?cè)诩軜?gòu)和實(shí)現(xiàn)的細(xì)節(jié)上有著顯著的不同。 在 AngularJS 中,你要把 AngularJS 的資源添加到 ?angular.module? 屬性上。 在 Angular 中,你要?jiǎng)?chuàng)建一個(gè)或多個(gè)帶有 ?NgModule ?裝飾器的類,這些裝飾器用來(lái)在元數(shù)據(jù)中描述 Angular 資源。差異主要來(lái)自這里。
在混合式應(yīng)用中,你同時(shí)運(yùn)行了兩個(gè)版本的 Angular。 這意味著你至少需要 AngularJS 和 Angular 各提供一個(gè)模塊。 當(dāng)你使用 AngularJS 的模塊進(jìn)行引導(dǎo)時(shí),就得把 Angular 的模塊傳給 ?UpgradeModule?。
引導(dǎo)混合應(yīng)用程序
要想引導(dǎo)混合式應(yīng)用,就必須在應(yīng)用中分別引導(dǎo) Angular 和 AngularJS 應(yīng)用的一部分。你必須先引導(dǎo) Angular,然后再調(diào)用 ?UpgradeModule ?來(lái)引導(dǎo) AngularJS。
在 AngularJS 應(yīng)用中有一個(gè) AngularJS 的根模塊,它用于引導(dǎo) AngularJS 應(yīng)用。
angular.module('heroApp', [])
.controller('MainCtrl', function() {
this.message = 'Hello world';
});單純的 AngularJS 應(yīng)用可以在 HTML 頁(yè)面中使用 ?ng-app? 指令進(jìn)行引導(dǎo),但對(duì)于混合式應(yīng)用你要通過(guò) ?UpgradeModule ?模塊進(jìn)行手動(dòng)引導(dǎo)。因此,在切換成混合式應(yīng)用之前,最好先把 AngularJS 改寫(xiě)成使用 angular.bootstrap 進(jìn)行手動(dòng)引導(dǎo)的方式。
比如你現(xiàn)在有這樣一個(gè)通過(guò) ?ng-app? 進(jìn)行引導(dǎo)的應(yīng)用:
{{ mainCtrl.message }}
你可以從 HTML 中移除 ?ng-app? 和 ?ng-strict-di? 指令,改為從 JavaScript 中調(diào)用 ?angular.bootstrap?,它能達(dá)到同樣效果:
angular.bootstrap(document.body, ['heroApp'], { strictDi: true });要想把 AngularJS 應(yīng)用變成 Hybrid 應(yīng)用,就要先加載 Angular 框架。 根據(jù)準(zhǔn)備升級(jí)到 AngularJS 中給出的步驟,選擇性的把快速入門(mén) github 代碼倉(cāng)中的代碼復(fù)制過(guò)來(lái)。
也可以通過(guò) ?npm install @angular/upgrade --save? 命令來(lái)安裝 ?@angular/upgrade? 包,并給它添加一個(gè)到 ?@angular/upgrade/static? 包的映射。
'@angular/upgrade/static': 'npm:@angular/upgrade/fesm2015/static.mjs',接下來(lái),創(chuàng)建一個(gè) ?app.module.ts? 文件,并添加下列 ?NgModule ?類:
import { DoBootstrap, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';
@NgModule({
imports: [
BrowserModule,
UpgradeModule
]
})
export class AppModule implements DoBootstrap {
constructor(private upgrade: UpgradeModule) { }
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
}最小化的 ?NgModule ?導(dǎo)入了 ?BrowserModule?,它是每個(gè)基于瀏覽器的 Angular 應(yīng)用必備的。 它還從 ?@angular/upgrade/static? 中導(dǎo)入了 ?UpgradeModule?,它導(dǎo)出了一些服務(wù)提供者,這些提供者會(huì)用于升級(jí)、降級(jí)服務(wù)和組件。
在 ?AppModule ?的構(gòu)造函數(shù)中,使用依賴注入技術(shù)獲取了一個(gè) ?UpgradeModule ?實(shí)例,并用它在 ?AppModule.ngDoBootstrap? 方法中啟動(dòng) AngularJS 應(yīng)用。 ?upgrade.bootstrap? 方法接受和 angular.bootstrap 完全相同的參數(shù)。
注意,你不需要在 ?
@NgModule? 中加入 ?bootstrap?聲明,因?yàn)?nbsp;AngularJS 控制著該應(yīng)用的根模板。
現(xiàn)在,你就可以使用 ?platformBrowserDynamic.bootstrapModule? 方法來(lái)啟動(dòng) ?AppModule ?了。
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
platformBrowserDynamic().bootstrapModule(AppModule);恭喜!你就要開(kāi)始運(yùn)行這個(gè)混合式應(yīng)用了!所有現(xiàn)存的 AngularJS 代碼會(huì)像以前一樣正常工作,但是你現(xiàn)在也同樣可以運(yùn)行 Angular 代碼了。
從 AngularJS 代碼中使用 Angular 組件
一旦你開(kāi)始運(yùn)行混合式應(yīng)用,你就可以開(kāi)始逐漸升級(jí)代碼了。一種更常見(jiàn)的工作模式就是在 AngularJS 的上下文中使用 Angular 的組件。 該組件可能是全新的,也可能是把原本 AngularJS 的組件用 Angular 重寫(xiě)而成的。
假設(shè)你有一個(gè)用來(lái)顯示英雄信息的 Angular 組件:
import { Component } from '@angular/core';
@Component({
selector: 'hero-detail',
template: `
Windstorm details!
1
`
})
export class HeroDetailComponent { }如果你想在 AngularJS 中使用這個(gè)組件,就得用 ?downgradeComponent()? 方法把它降級(jí)。 其結(jié)果是一個(gè) AngularJS 的指令,你可以把它注冊(cè)到 AngularJS 的模塊中:
import { HeroDetailComponent } from './hero-detail.component';
/* . . . */
import { downgradeComponent } from '@angular/upgrade/static';
angular.module('heroApp', [])
.directive(
'heroDetail',
downgradeComponent({ component: HeroDetailComponent }) as angular.IDirectiveFactory
);
默認(rèn)情況下,Angular 變更檢測(cè)也會(huì)在 AngularJS 的每個(gè) ?
$digest? 周期中運(yùn)行。如果你希望只在輸入屬性發(fā)生變化時(shí)才運(yùn)行變更檢測(cè),可以在調(diào)用 ?downgradeComponent()? 時(shí)把 ?propagateDigest?設(shè)置為 ?false?。
由于 ?HeroDetailComponent ?是一個(gè) Angular 組件,所以你必須同時(shí)把它加入 ?AppModule ?的 ?declarations ?字段中。
import { HeroDetailComponent } from './hero-detail.component';
@NgModule({
imports: [
BrowserModule,
UpgradeModule
],
declarations: [
HeroDetailComponent
]
})
export class AppModule implements DoBootstrap {
constructor(private upgrade: UpgradeModule) { }
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
}
所有 Angular 組件、指令和管道都必須聲明在 NgModule 中。
最終的結(jié)果是一個(gè)叫做 ?heroDetail ?的 AngularJS 指令,你可以像用其它指令一樣把它用在 AngularJS 模板中。
注意,它在 AngularJS 中是一個(gè)名叫 ?
heroDetail?的元素型指令(?restrict: 'E'?)。 AngularJS 的元素型指令是基于它的名字匹配的。 Angular 組件中的 ?selector?元數(shù)據(jù),在降級(jí)后的版本中會(huì)被忽略。
當(dāng)然,大多數(shù)組件都不像這個(gè)這么簡(jiǎn)單。它們中很多都有輸入屬性和輸出屬性,來(lái)把它們連接到外部世界。 Angular 的英雄詳情組件帶有像這樣的輸入屬性與輸出屬性:
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Hero } from '../hero';
@Component({
selector: 'hero-detail',
template: `
{{hero.name}} details!
{{hero.id}}
`
})
export class HeroDetailComponent {
@Input() hero!: Hero;
@Output() deleted = new EventEmitter();
onDelete() {
this.deleted.emit(this.hero);
}
} 這些輸入屬性和輸出屬性的值來(lái)自于 AngularJS 的模板,而 ?downgradeComponent()? 方法負(fù)責(zé)橋接它們:
注意,雖然你正在 AngularJS 的模板中,但卻在使用 Angular 的屬性(Attribute)語(yǔ)法來(lái)綁定到輸入屬性與輸出屬性。 這是降級(jí)的組件本身要求的。而表達(dá)式本身仍然是標(biāo)準(zhǔn)的 AngularJS 表達(dá)式。
在降級(jí)過(guò)的組件屬性中使用中線命名法
為降級(jí)過(guò)的組件使用 Angular 的屬性(Attribute)語(yǔ)法規(guī)則時(shí)有一個(gè)值得注意的例外。 它適用于由多個(gè)單詞組成的輸入或輸出屬性。在 Angular 中,你要使用小駝峰命名法綁定這些屬性:[myHero]="hero" (heroDeleted)="handleHeroDeleted($event)"但是從 AngularJS 的模板中使用它們時(shí),你得使用中線命名法:
[my-hero]="hero" (hero-deleted)="handleHeroDeleted($event)"
?$event? 變量能被用在輸出屬性里,以訪問(wèn)這個(gè)事件所發(fā)出的對(duì)象。這個(gè)案例中它是 ?Hero ?對(duì)象,因?yàn)?nbsp;?this.deleted.emit()? 函數(shù)曾把它傳了出來(lái)。
由于這是一個(gè) AngularJS 模板,雖然它已經(jīng)有了 Angular 中綁定的屬性(Attribute),你仍可以在這個(gè)元素上使用其它 AngularJS 指令。 例如,你可以用 ?ng-repeat? 簡(jiǎn)單的制作該組件的多份拷貝:
從 Angular 代碼使用 AngularJS 組件指令
現(xiàn)在,你已經(jīng)能在 Angular 中寫(xiě)一個(gè)組件,并把它用于 AngularJS 代碼中了。 當(dāng)你從低級(jí)組件開(kāi)始移植,并往上走時(shí),這非常有用。但在另外一些情況下,從相反的方向進(jìn)行移植會(huì)更加方便: 從高級(jí)組件開(kāi)始,然后往下走。這也同樣能用 ?UpgradeModule ?完成。 你可以升級(jí)AngularJS 組件型指令,然后從 Angular 中用它們。
不是所有種類的 AngularJS 指令都能升級(jí)。該指令必須是一個(gè)嚴(yán)格的組件型指令,具有上面的準(zhǔn)備指南中描述的那些特征。 確保兼容性的最安全的方式是 AngularJS 1.5 中引入的組件 API。
可升級(jí)組件的簡(jiǎn)單例子是只有一個(gè)模板和一個(gè)控制器的指令:
export const heroDetail = {
template: `
Windstorm details!
1
`,
controller: function HeroDetailController() {
}
};你可以使用 ?UpgradeComponent ?方法來(lái)把這個(gè)組件升級(jí)到 Angular。 具體方法是創(chuàng)建一個(gè) Angular指令,繼承 ?UpgradeComponent?,在其構(gòu)造函數(shù)中進(jìn)行 ?super ?調(diào)用, 這樣你就得到一個(gè)完全升級(jí)的 AngularJS 組件,并且可以 Angular 中使用。 剩下是工作就是把它加入到 ?AppModule ?的 ?declarations ?數(shù)組。
import { Directive, ElementRef, Injector, SimpleChanges } from '@angular/core';
import { UpgradeComponent } from '@angular/upgrade/static';
@Directive({
selector: 'hero-detail'
})
export class HeroDetailDirective extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) {
super('heroDetail', elementRef, injector);
}
}
@NgModule({
imports: [
BrowserModule,
UpgradeModule
],
declarations: [
HeroDetailDirective,
/* . . . */
]
})
export class AppModule implements DoBootstrap {
constructor(private upgrade: UpgradeModule) { }
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
}
升級(jí)后的組件是 Angular 的指令,而不是組件,因?yàn)?nbsp;Angular 不知道 AngularJS 將在它下面創(chuàng)建元素。 Angular 所知道的是升級(jí)后的組件只是一個(gè)指令(一個(gè)標(biāo)簽),Angular 不需要關(guān)心組件本身及其子元素。
升級(jí)后的組件也可能有輸入屬性和輸出屬性,它們是在原 AngularJS 組件型指令的 scope/controller 綁定中定義的。 當(dāng)你從 Angular 模板中使用該組件時(shí),就要使用Angular 模板語(yǔ)法來(lái)提供這些輸入屬性和輸出屬性,但要遵循下列規(guī)則:
|
綁定定義 |
模板語(yǔ)法 |
|
|---|---|---|
屬性綁定 | myAttribute: '@myAttribute' | |
表達(dá)式綁定 | myOutput: '&myOutput' | |
單向綁定 | myValue: ' | |
雙向綁定 | myValue: '=myValue' | 用作雙向綁定: |
舉個(gè)例子,假設(shè) AngularJS 中有一個(gè)表示“英雄詳情”的組件型指令,它帶有一個(gè)輸入屬性和一個(gè)輸出屬性:
export const heroDetail = {
bindings: {
hero: '<',
deleted: '&'
},
template: `
{{$ctrl.hero.name}} details!
{{$ctrl.hero.id}}
`,
controller: function HeroDetailController() {
this.onDelete = () => {
this.deleted(this.hero);
};
}
};你可以把這個(gè)組件升級(jí)到 Angular,然后使用 Angular 的模板語(yǔ)法提供這個(gè)輸入屬性和輸出屬性:
import { Directive, ElementRef, Injector, Input, Output, EventEmitter } from '@angular/core';
import { UpgradeComponent } from '@angular/upgrade/static';
import { Hero } from '../hero';
@Directive({
selector: 'hero-detail'
})
export class HeroDetailDirective extends UpgradeComponent {
@Input() hero: Hero;
@Output() deleted: EventEmitter;
constructor(elementRef: ElementRef, injector: Injector) {
super('heroDetail', elementRef, injector);
}
}
import { Component } from '@angular/core';
import { Hero } from '../hero';
@Component({
selector: 'my-container',
template: `
Tour of Heroes
`
})
export class ContainerComponent {
hero = new Hero(1, 'Windstorm');
heroDeleted(hero: Hero) {
hero.name = 'Ex-' + hero.name;
}
}
把 AngularJS 的內(nèi)容投影到 Angular 組件中
如果你在 AngularJS 模板中使用降級(jí)后的 Angular 組件時(shí),可能會(huì)需要把模板中的一些內(nèi)容投影進(jìn)那個(gè)組件。 這也是可能的,雖然在 Angular 中并沒(méi)有透?jìng)?transclude)這樣的東西,但它有一個(gè)非常相似的概念,叫做內(nèi)容投影。 ?UpgradeModule ?也能讓這兩個(gè)特性實(shí)現(xiàn)互操作。
Angular 的組件通過(guò)使用 ?? 標(biāo)簽來(lái)支持內(nèi)容投影。下面是這類組件的一個(gè)例子:
import { Component, Input } from '@angular/core';
import { Hero } from '../hero';
@Component({
selector: 'hero-detail',
template: `
{{hero.name}}
`
})
export class HeroDetailComponent {
@Input() hero!: Hero;
}當(dāng)從 AngularJS 中使用該組件時(shí),你可以為它提供內(nèi)容。正如它們將在 AngularJS 中被透?jìng)饕粯樱?nbsp;它們也在 Angular 中被投影到了 ?? 標(biāo)簽所在的位置:
{{mainCtrl.hero.description}}
當(dāng) AngularJS 的內(nèi)容被投影到 Angular 組件中時(shí),它仍然留在“AngularJS 王國(guó)”中,并被 AngularJS 框架管理著。
把 Angular 的內(nèi)容透?jìng)鬟M(jìn) AngularJS 的組件型指令
就像可以把 AngularJS 的內(nèi)容投影進(jìn) Angular 組件一樣,你也能把 Angular 的內(nèi)容透?jìng)?/b>進(jìn) AngularJS 的組件, 但不管怎樣,你都要使用它們升級(jí)過(guò)的版本。
如果一個(gè) AngularJS 組件型指令支持透?jìng)?,它就?huì)在自己的模板中使用 ?ng-transclude? 指令標(biāo)記出透?jìng)鞯降奈恢茫?/p>
export const heroDetail = {
bindings: {
hero: '='
},
template: `
{{$ctrl.hero.name}}
`,
transclude: true
};如果你升級(jí)這個(gè)組件,并把它用在 Angular 中,你就能把準(zhǔn)備透?jìng)鞯膬?nèi)容放進(jìn)這個(gè)組件的標(biāo)簽中。
import { Component } from '@angular/core';
import { Hero } from '../hero';
@Component({
selector: 'my-container',
template: `
{{hero.description}}
`
})
export class ContainerComponent {
hero = new Hero(1, 'Windstorm', 'Specific powers of controlling winds');
}
讓 AngularJS 中的依賴可被注入到 Angular
當(dāng)運(yùn)行一個(gè)混合式應(yīng)用時(shí),可能會(huì)遇到這種情況:你需要把某些 AngularJS 的依賴注入到 Angular 代碼中。 這可能是因?yàn)槟承I(yè)務(wù)邏輯仍然在 AngularJS 服務(wù)中,或者需要某些 AngularJS 的內(nèi)置服務(wù),比如 ?$location? 或 ?$timeout?。
在這些情況下,把一個(gè) AngularJS 提供者升級(jí)到Angular 也是有可能的。這就讓它將來(lái)有可能被注入到 Angular 代碼中的某些地方。 比如,你可能在 AngularJS 中有一個(gè)名叫 ?HeroesService ?的服務(wù):
import { Hero } from '../hero';
export class HeroesService {
get() {
return [
new Hero(1, 'Windstorm'),
new Hero(2, 'Spiderman')
];
}
}你可以用 Angular 的工廠提供者升級(jí)該服務(wù), 它從 AngularJS 的 ?$injector? 請(qǐng)求服務(wù)。
很多開(kāi)發(fā)者都喜歡在一個(gè)獨(dú)立的 ?ajs-upgraded-providers.ts? 中聲明這個(gè)工廠提供者,以便把它們都放在一起,這樣便于引用、創(chuàng)建新的以及在升級(jí)完畢時(shí)刪除它們。
同時(shí),建議導(dǎo)出 ?heroesServiceFactory ?函數(shù),以便 AOT 編譯器可以拿到它們。
注意:這個(gè)工廠中的字符串 'heroes' 指向的是 AngularJS 的 ?
HeroesService?。 AngularJS 應(yīng)用中通常使用服務(wù)名作為令牌,比如 'heroes',并為其追加 'Service' 后綴來(lái)創(chuàng)建其類名。
import { HeroesService } from './heroes.service';
export function heroesServiceFactory(i: any) {
return i.get('heroes');
}
export const heroesServiceProvider = {
provide: HeroesService,
useFactory: heroesServiceFactory,
deps: ['$injector']
};然后,你就可以把這個(gè)服務(wù)添加到 ?@NgModule? 中來(lái)把它暴露給 Angular:
import { heroesServiceProvider } from './ajs-upgraded-providers';
@NgModule({
imports: [
BrowserModule,
UpgradeModule
],
providers: [
heroesServiceProvider
],
/* . . . */
})
export class AppModule implements DoBootstrap {
constructor(private upgrade: UpgradeModule) { }
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
}然后在組件的構(gòu)造函數(shù)中使用該服務(wù)的類名作為類型注解注入到組件中,從而在組件中使用它:
import { Component } from '@angular/core';
import { HeroesService } from './heroes.service';
import { Hero } from '../hero';
@Component({
selector: 'hero-detail',
template: `
{{hero.id}}: {{hero.name}}
`
})
export class HeroDetailComponent {
hero: Hero;
constructor(heroes: HeroesService) {
this.hero = heroes.get()[0];
}
}
在這個(gè)例子中,你升級(jí)了服務(wù)類。當(dāng)注入它時(shí),你可以使用 TypeScript 類型注解來(lái)獲得這些額外的好處。 它沒(méi)有影響該依賴的處理過(guò)程,同時(shí)還得到了啟用靜態(tài)類型檢查的好處。 任何 AngularJS 中的服務(wù)、工廠和提供者都能被升級(jí) —— 盡管這不是必須的。
讓 Angular 的依賴能被注入到 AngularJS 中
除了能升級(jí) AngularJS 依賴之外,你還能降級(jí)Angular 的依賴,以便在 AngularJS 中使用它們。 當(dāng)你已經(jīng)開(kāi)始把服務(wù)移植到 Angular 或在 Angular 中創(chuàng)建新服務(wù),但同時(shí)還有一些用 AngularJS 寫(xiě)成的組件時(shí),這會(huì)非常有用。
例如,你可能有一個(gè) Angular 的 ?Heroes ?服務(wù):
import { Injectable } from '@angular/core';
import { Hero } from '../hero';
@Injectable()
export class Heroes {
get() {
return [
new Hero(1, 'Windstorm'),
new Hero(2, 'Spiderman')
];
}
}仿照 Angular 組件,把該提供者加入 ?NgModule ?的 ?providers ?列表中,以注冊(cè)它。
import { Heroes } from './heroes';
@NgModule({
imports: [
BrowserModule,
UpgradeModule
],
providers: [ Heroes ]
})
export class AppModule implements DoBootstrap {
constructor(private upgrade: UpgradeModule) { }
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
}現(xiàn)在,用 ?downgradeInjectable()? 來(lái)把 Angular 的 ?Heroes ?包裝成AngularJS 的工廠函數(shù),并把這個(gè)工廠注冊(cè)進(jìn) AngularJS 的模塊中。 依賴在 AngularJS 中的名字你可以自己定:
import { Heroes } from './heroes';
/* . . . */
import { downgradeInjectable } from '@angular/upgrade/static';
angular.module('heroApp', [])
.factory('heroes', downgradeInjectable(Heroes))
.component('heroDetail', heroDetailComponent);此后,該服務(wù)就能被注入到 AngularJS 代碼中的任何地方了:
export const heroDetailComponent = {
template: `
{{$ctrl.hero.id}}: {{$ctrl.hero.name}}
`,
controller: ['heroes', function(heroes: Heroes) {
this.hero = heroes.get()[0];
}]
};
惰性加載 AngularJS
在構(gòu)建應(yīng)用時(shí),你需要確保只在必要的時(shí)候才加載所需的資源,無(wú)論是加載靜態(tài)資產(chǎn)(Asset)還是代碼。要確保任何事都盡量推遲到必要時(shí)才去做,以便讓?xiě)?yīng)用更高效的運(yùn)行。當(dāng)要在同一個(gè)應(yīng)用中運(yùn)行不同的框架時(shí),更是如此。
惰性加載是一項(xiàng)技術(shù),它會(huì)推遲到使用時(shí)才加載所需靜態(tài)資產(chǎn)和代碼資源。這可以減少啟動(dòng)時(shí)間、提高效率,特別是要在同一個(gè)應(yīng)用中運(yùn)行不同的框架時(shí)。
當(dāng)你采用混合式應(yīng)用的方式將大型應(yīng)用從 AngularJS 遷移到 Angular 時(shí),你首先要遷移一些最常用的特性,并且只在必要的時(shí)候才使用那些不太常用的特性。這樣做有助于確保應(yīng)用程序在遷移過(guò)程中仍然能為用戶提供無(wú)縫的體驗(yàn)。
在大多數(shù)需要同時(shí)用 Angular 和 AngularJS 渲染應(yīng)用的環(huán)境中,這兩個(gè)框架都會(huì)包含在發(fā)送給客戶端的初始發(fā)布包中。這會(huì)導(dǎo)致發(fā)布包的體積增大、性能降低。
當(dāng)用戶停留在由 Angular 渲染的頁(yè)面上時(shí),應(yīng)用的整體性能也會(huì)受到影響。這是因?yàn)?nbsp;AngularJS 的框架和應(yīng)用仍然被加載并運(yùn)行了 —— 即使它們從未被訪問(wèn)過(guò)。
你可以采取一些措施來(lái)緩解這些包的大小和性能問(wèn)題。通過(guò)把 AngularJS 應(yīng)用程序分離到一個(gè)單獨(dú)的發(fā)布包中,你就可以利用惰性加載技術(shù)來(lái)只在必要的時(shí)候才加載、引導(dǎo)和渲染這個(gè) AngularJS 應(yīng)用。這種策略減少了你的初始發(fā)布包大小,推遲了同時(shí)加載兩個(gè)框架的潛在影響 —— 直到絕對(duì)必要時(shí)才加載,以便讓你的應(yīng)用盡可能高效地運(yùn)行。
下面的步驟介紹了應(yīng)該如何去做:
- 為 AngularJS 發(fā)布包設(shè)置一個(gè)回調(diào)函數(shù)。
- 創(chuàng)建一個(gè)服務(wù),以便惰性加載并引導(dǎo)你的 AngularJS 應(yīng)用。
- 為 AngularJS 內(nèi)容創(chuàng)建一個(gè)可路由的組件
- 為 AngularJS 特有的 URL 創(chuàng)建自定義的 ?
matcher?函數(shù),并為 AngularJS 的各個(gè)路由配上帶有自定義匹配器的 Angular 路由器。
為惰性加載 AngularJS 創(chuàng)建一個(gè)服務(wù)
在 Angular 的版本 8 中,惰性加載代碼只需使用動(dòng)態(tài)導(dǎo)入語(yǔ)法 ?import('...')? 即可。在這個(gè)應(yīng)用中,你創(chuàng)建了一個(gè)新服務(wù),它使用動(dòng)態(tài)導(dǎo)入技術(shù)來(lái)惰性加載 AngularJS。
import { Injectable } from '@angular/core';
import * as angular from 'angular';
@Injectable({
providedIn: 'root'
})
export class LazyLoaderService {
private app: angular.auto.IInjectorService | undefined;
load(el: HTMLElement): void {
import('./angularjs-app').then(app => {
try {
this.app = app.bootstrap(el);
} catch (e) {
console.error(e);
}
});
}
destroy() {
if (this.app) {
this.app.get('$rootScope').$destroy();
}
}
}該服務(wù)使用 ?import()? 方法惰性加載打包好的 AngularJS 應(yīng)用。這會(huì)減少應(yīng)用初始包的大小,因?yàn)槟闵形醇虞d用戶目前不需要的代碼。你還要提供一種方法,在加載完畢后手動(dòng)啟動(dòng)它。AngularJS 提供了一種使用 angular.bootstrap() 方法并傳入一個(gè) HTML 元素來(lái)手動(dòng)引導(dǎo)應(yīng)用的方法。你的 AngularJS 應(yīng)用也應(yīng)該公開(kāi)一個(gè)用來(lái)引導(dǎo) AngularJS 應(yīng)用的 ?bootstrap ?方法。
要確保 AngularJS 應(yīng)用中的任何清理工作都觸發(fā)過(guò)(比如移除全局監(jiān)聽(tīng)器),你還可以實(shí)現(xiàn)一個(gè)方法來(lái)調(diào)用 ?$rootScope.destroy()? 方法。
import * as angular from 'angular';
import 'angular-route';
const appModule = angular.module('myApp', [
'ngRoute'
])
.config(['$routeProvider', '$locationProvider',
function config($routeProvider: angular.route.IRouteProvider,
$locationProvider: angular.ILocationProvider) {
$locationProvider.html5Mode(true);
$routeProvider.
when('/users', {
template: `
Users Page
`
}).
otherwise({
template: ''
});
}]
);
export function bootstrap(el: HTMLElement) {
return angular.bootstrap(el, [appModule.name]);
}你的 AngularJS 應(yīng)用只配置了渲染內(nèi)容所需的那部分路由。而 Angular 路由器會(huì)處理應(yīng)用中其余的路由。你的 Angular 應(yīng)用中會(huì)調(diào)用公開(kāi)的 ?bootstrap ?方法,讓它在加載完發(fā)布包之后引導(dǎo) AngularJS 應(yīng)用。
注意:當(dāng) AngularJS 加載并引導(dǎo)完畢后,監(jiān)聽(tīng)器(比如路由配置中的那些監(jiān)聽(tīng)器)會(huì)繼續(xù)監(jiān)聽(tīng)路由的變化。為了確保當(dāng) AngularJS 尚未顯示時(shí)先關(guān)閉監(jiān)聽(tīng)器,請(qǐng)?jiān)?nbsp;$routeProvider 中配置一個(gè)渲染空模板 ?
otherwise?選項(xiàng)。這里假設(shè) Angular 將處理所有其它路由。
創(chuàng)建一個(gè)用來(lái)渲染 AngularJS 內(nèi)容的組件
在 Angular 應(yīng)用中,你需要一個(gè)組件作為 AngularJS 內(nèi)容的占位符。該組件使用你創(chuàng)建的服務(wù),并在組件初始化完成后加載并引導(dǎo)你的 AngularJS 應(yīng)用。
import { Component, OnInit, OnDestroy, ElementRef } from '@angular/core';
import { LazyLoaderService } from '../lazy-loader.service';
@Component({
selector: 'app-angular-js',
template: ''
})
export class AngularJSComponent implements OnInit, OnDestroy {
constructor(
private lazyLoader: LazyLoaderService,
private elRef: ElementRef
) {}
ngOnInit() {
this.lazyLoader.load(this.elRef.nativeElement);
}
ngOnDestroy() {
this.lazyLoader.destroy();
}
}當(dāng) Angular 的路由器匹配到使用 AngularJS 的路由時(shí),會(huì)渲染 AngularJSComponent,并在 AngularJS 的 ng-view 指令中渲染內(nèi)容。當(dāng)用戶導(dǎo)航離開(kāi)本路由時(shí),$rootScope 會(huì)在 AngularJS 應(yīng)用中被銷毀。
為那些 AngularJS 路由配置自定義路由匹配器
為了配置 Angular 的路由器,你必須為 AngularJS 的 URL 定義路由。要匹配這些 URL,你需要添加一個(gè)使用 ?matcher ?屬性的路由配置。這個(gè) ?matcher ?允許你使用自定義模式來(lái)匹配這些 URL 路徑。Angular 的路由器會(huì)首先嘗試匹配更具體的路由,比如靜態(tài)路由和可變路由。當(dāng)它找不到匹配項(xiàng)時(shí),就會(huì)求助于路由配置中的自定義匹配器。如果自定義匹配器與某個(gè)路由不匹配,它就會(huì)轉(zhuǎn)到用于 "捕獲所有"(catch-all)的路由,比如 404 頁(yè)面。
下面的例子給 AngularJS 路由定義了一個(gè)自定義匹配器函數(shù)。
export function isAngularJSUrl(url: UrlSegment[]) {
return url.length > 0 && url[0].path.startsWith('users') ? ({consumed: url}) : null;
}下列代碼往你的路由配置中添加了一個(gè)路由對(duì)象,其 ?matcher ?屬性是這個(gè)自定義匹配器,而 ?component ?屬性為 ?AngularJSComponent?。
import { NgModule } from '@angular/core';
import { Routes, RouterModule, UrlSegment } from '@angular/router';
import { AngularJSComponent } from './angular-js/angular-js.component';
import { HomeComponent } from './home/home.component';
import { App404Component } from './app404/app404.component';
// Match any URL that starts with `users`
export function isAngularJSUrl(url: UrlSegment[]) {
return url.length > 0 && url[0].path.startsWith('users') ? ({consumed: url}) : null;
}
export const routes: Routes = [
// Routes rendered by Angular
{ path: '', component: HomeComponent },
// AngularJS routes
{ matcher: isAngularJSUrl, component: AngularJSComponent },
// Catch-all route
{ path: '**', component: App404Component }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }當(dāng)你的應(yīng)用匹配上需要 AngularJS 的路由時(shí),AngularJS 應(yīng)用就會(huì)被加載并引導(dǎo)。AngularJS 路由會(huì)匹配必要的 URL 以渲染它們的內(nèi)容,而接下來(lái)你的應(yīng)用就會(huì)同時(shí)運(yùn)行 AngularJS 和 Angular 框架。
使用統(tǒng)一的 Angular 位置服務(wù)(Location)
在 AngularJS 中,$location 服務(wù)會(huì)處理所有路由配置和導(dǎo)航工作,并對(duì)各個(gè) URL 進(jìn)行編碼和解碼、重定向、以及與瀏覽器 API 交互。Angular 在所有這些任務(wù)中都使用了自己的底層服務(wù) ?Location?。
當(dāng)你從 AngularJS 遷移到 Angular 時(shí),你會(huì)希望把盡可能多的責(zé)任移交給 Angular,以便利用新的 API。為了幫你完成這種轉(zhuǎn)換,Angular 提供了 ?LocationUpgradeModule?。該模塊支持統(tǒng)一位置服務(wù),可以把 AngularJS 中 ?$location? 服務(wù)的職責(zé)轉(zhuǎn)給 Angular 的 ?Location ?服務(wù)。
要使用 ?LocationUpgradeModule?,就會(huì)從 ?@angular/common/upgrade? 中導(dǎo)入此符號(hào),并使用靜態(tài)方法 ?LocationUpgradeModule.config()? 把它添加到你的 ?AppModule ?導(dǎo)入表(?imports?)中。
// Other imports ...
import { LocationUpgradeModule } from '@angular/common/upgrade';
@NgModule({
imports: [
// Other NgModule imports...
LocationUpgradeModule.config()
]
})
export class AppModule {}?LocationUpgradeModule.config()? 方法接受一個(gè)配置對(duì)象,該對(duì)象的 ?useHash ?為 ?LocationStrategy?,?hashPrefix ?為 URL 前綴。
?useHash ?屬性默認(rèn)為 ?false?,而 ?hashPrefix ?默認(rèn)為空 ?string?。傳遞配置對(duì)象可以覆蓋默認(rèn)值。
LocationUpgradeModule.config({
useHash: true,
hashPrefix: '!'
})這會(huì)為 AngularJS 中的 ?$location? 提供者注冊(cè)一個(gè)替代品。一旦注冊(cè)成功,導(dǎo)航過(guò)程中所有由 AngularJS 觸發(fā)的導(dǎo)航、路由廣播消息以及任何必需的變更檢測(cè)周期都會(huì)改由 Angular 進(jìn)行處理。這樣,你就可以通過(guò)這個(gè)唯一的途徑在此混合應(yīng)用的兩個(gè)框架間進(jìn)行導(dǎo)航了。
要想在 AngularJS 中使用 ?$location? 服務(wù)作為提供者,你需要使用一個(gè)工廠提供者來(lái)降級(jí) ?$locationShim?。
// Other imports ...
import { $locationShim } from '@angular/common/upgrade';
import { downgradeInjectable } from '@angular/upgrade/static';
angular.module('myHybridApp', [...])
.factory('$location', downgradeInjectable($locationShim));一旦引入了 Angular 路由器,你只要使用 Angular 路由器就可以通過(guò)統(tǒng)一位置服務(wù)來(lái)觸發(fā)導(dǎo)航了,同時(shí),你仍然可以通過(guò) AngularJS 和 Angular 進(jìn)行導(dǎo)航。
PhoneCat 升級(jí)教程
在本節(jié)和下節(jié)中,你將看一個(gè)完整的例子,它使用 ?upgrade ?模塊準(zhǔn)備和升級(jí)了一個(gè)應(yīng)用程序。 該應(yīng)用就是來(lái)自原 AngularJS 教程中的Angular PhoneCat。 那是我們很多人當(dāng)初開(kāi)始 Angular 探險(xiǎn)之旅的地方。 現(xiàn)在,你會(huì)看到如何把該應(yīng)用帶入 Angular 的美麗新世界。
這期間,你將學(xué)到如何在實(shí)踐中應(yīng)用準(zhǔn)備指南中列出的那些重點(diǎn)步驟: 你先讓該應(yīng)用向 Angular 看齊,然后為它引入 SystemJS 模塊加載器和 TypeScript。
本教程基于 ?angular-phonecat? 教程的 1.5.x 版本,該教程保存在代碼倉(cāng)庫(kù)的 1.5-snapshot 分支中。接下來(lái),克隆 angular-phonecat 代碼倉(cāng)庫(kù),check out ?1.5-snapshot? 分支并應(yīng)用這些步驟。
在項(xiàng)目結(jié)構(gòu)方面,工作的起點(diǎn)是這樣的:
這確實(shí)是一個(gè)很好地起點(diǎn)。這些代碼使用了 AngularJS 1.5 的組件 API,并遵循了 AngularJS 風(fēng)格指南進(jìn)行組織, 在成功升級(jí)之前,這是一個(gè)很重要的準(zhǔn)備步驟。
- 每個(gè)組件、服務(wù)和過(guò)濾器都在它自己的源文件中 —— 就像單一規(guī)則所要求的。
- ?
core?、?phone-detail? 和 ?phone-list? 模塊都在它們自己的子目錄中。那些子目錄除了包含 HTML 模板之外,還包含 JavaScript 代碼,它們共同完成一個(gè)特性。 這是按特性分目錄的結(jié)構(gòu) 和模塊化規(guī)則所要求的。 - 單元測(cè)試都和應(yīng)用代碼在一起,它們很容易找到。就像規(guī)則 組織測(cè)試文件中要求的那樣。
切換到 TypeScript
因?yàn)槟銓⑹褂?nbsp;TypeScript 編寫(xiě) Angular 的代碼,所以在開(kāi)始升級(jí)之前,先要把 TypeScript 的編譯器設(shè)置好。
你還將開(kāi)始逐步淘汰 Bower 包管理器,換成 NPM。后面你將使用 NPM 來(lái)安裝新的依賴包,并最終從項(xiàng)目中移除 Bower。
先把 TypeScript 包安裝到項(xiàng)目中。
npm i typescript --save-dev還要為那些沒(méi)有自帶類型信息的庫(kù)(比如 AngularJS、AngularJS Material 和 Jasmine)安裝類型定義文件。
對(duì)于 PhoneCat 應(yīng)用,我們可以運(yùn)行下列命令來(lái)安裝必要的類型定義文件:
npm install @types/jasmine @types/angular @types/angular-animate @types/angular-aria @types/angular-cookies @types/angular-mocks @types/angular-resource @types/angular-route @types/angular-sanitize --save-dev如果你正在使用 AngularJS Material,你可以通過(guò)下列命令安裝其類型定義:
npm install @types/angular-material --save-dev你還應(yīng)該要往項(xiàng)目目錄下添加一個(gè) ?tsconfig.json? 文件,?tsconfig.json? 文件會(huì)告訴 TypeScript 編譯器如何把 TypeScript 文件轉(zhuǎn)成 ES5 代碼,并打包進(jìn) CommonJS 模塊中。
最后,你應(yīng)該把下列 npm 腳本添加到 ?package.json? 中,用于把 TypeScript 文件編譯成 JavaScript (根據(jù) ?tsconfig.json? 的配置):
"scripts": {
"tsc": "tsc",
"tsc:w": "tsc -w",
...現(xiàn)在,從命令行中用監(jiān)視模式啟動(dòng) TypeScript 編譯器:
npm run tsc:w讓這個(gè)進(jìn)程一直在后臺(tái)運(yùn)行,監(jiān)聽(tīng)任何變化并自動(dòng)重新編譯。
接下來(lái),把 JavaScript 文件轉(zhuǎn)換成 TypeScript 文件。 由于 TypeScript 是 ECMAScript 2015 的一個(gè)超集,而 ES2015 又是 ECMAScript 5 的超集,所以你可以簡(jiǎn)單的把文件的擴(kuò)展名從 ?.js? 換成 ?.ts?, 它們還是會(huì)像以前一樣工作。由于 TypeScript 編譯器仍在運(yùn)行,它會(huì)為每一個(gè) ?.ts? 文件生成對(duì)應(yīng)的 ?.js? 文件,而真正運(yùn)行的是編譯后的 ?.js? 文件。 如果你用 ?npm start? 開(kāi)啟了本項(xiàng)目的 HTTP 服務(wù)器,你會(huì)在瀏覽器中看到一個(gè)功能完好的應(yīng)用。
有了 TypeScript,你就可以從它的一些特性中獲益了。此語(yǔ)言可以為 AngularJS 應(yīng)用提供很多價(jià)值。
首先,TypeScript 是一個(gè) ES2015 的超集。任何以前用 ES5 寫(xiě)的程序(就像 PhoneCat 范例)都可以開(kāi)始通過(guò) TypeScript 納入那些添加到 ES2015 中的新特性。 這包括 ?let?、?const?、箭頭函數(shù)、函數(shù)默認(rèn)參數(shù)以及解構(gòu)(destructure)賦值。
你能做的另一件事就是把類型安全添加到代碼中。這實(shí)際上已經(jīng)部分完成了,因?yàn)槟阋呀?jīng)安裝了 AngularJS 的類型定義。 TypeScript 會(huì)幫你檢查是否正確調(diào)用了 AngularJS 的 API,—— 比如往 Angular 模塊中注冊(cè)組件。
你還能開(kāi)始把類型注解添加到自己的代碼中,來(lái)從 TypeScript 的類型系統(tǒng)中獲得更多幫助。 比如,你可以給 ?checkmark ?過(guò)濾器加上注解,表明它期待一個(gè) ?boolean ?類型的參數(shù)。 這可以更清楚的表明此過(guò)濾器打算做什么
angular.
module('core').
filter('checkmark', () => {
return (input: boolean) => input ? '\u2713' : '\u2718';
});在 ?Phone ?服務(wù)中,你可以明確的把 ?$resource?&nb
分享標(biāo)題:創(chuàng)新互聯(lián)Angular教程:Angular升級(jí)說(shuō)明
網(wǎng)站網(wǎng)址:http://m.fisionsoft.com.cn/article/djgeojo.html


咨詢
建站咨詢
