掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
Angular是現(xiàn)在和未來的 Angular 名稱。

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

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流