使用 Angular 2 實現單頁應用程序
由于具有實現更高性能的瀏覽器和智能電話應用程序的潛力,單頁應用程序 (SPA) 技術在軟件行業引起了廣泛的興趣。在過去 5 年多的時間里,開發人員對 Angular (一個開源 SPA 框架)的興趣遠超他們對其他 Web 框架(React、Backbone、Meteor 和 Ember)的興趣,這從 StackOverflow 網站上針對每種 Web 框架的問題數量上可以判斷出:
Web 和移動開發人員非常喜歡 Angular 2(2016 年 9 月發布)。Angular 2 不是 Angular 1 的一次升級,而是一個全新的、不同的、更高級的框架。精通 Angular 2 已成為構建高性能、可擴展、穩健、現代的跨平臺應用程序的一種很吃香的技能。
TypeScript
Angular 2 支持 JavaScript、Dart 和 TypeScript。您將使用 TypeScript 作為本教程的項目的開發語言。該框架構建于 TypeScript 之上,大多數有關 Angular 的文檔、圖書和教程都側重于將 TypeScript 作為開發語言。
本教程將介紹 Angular 2 的重要構建基塊,演示如何在開發計算機和沙箱服務器中使用 Angular 2 編寫和運行 SPA。要充分掌握本教程,您需要具有一定的 Web 編程經驗,包括 JavaScript、TypeScript 和 HTML 的各方面知識。無需提前擁有 Angular 經驗。練習完示例項目后,您就會為使用 Angular 2 創建自己的 SPA 做好充足的準備。
可以從教程末尾的 “可下載資源” 部分下載針對沙箱應用程序的完整示例代碼。
為什么使用 SPA 和 Angular 2?
在用戶啟動 SPA 時,該應用程序僅呈現來自服務器的一個 HTML 頁面。除了這個 HTML 頁面,服務器還會向客戶端發送一個應用程序引擎。該引擎控制整個應用程序,包括HTML 頁面的處理、輸入、輸出、繪制和加載。通常,90–95% 的應用程序代碼是在瀏覽器中運行;當用戶需要新數據時或必須執行身份驗證等安全操作時,就會在服務器中運行剩余代碼。由于幾乎消除了對服務器的依賴,所以 SPA 能在 Angular 2 環境中自動擴展:無論有多少用戶同時訪問服務器,在 90–95% 的時間里,應用程序的性能絕不會受到影響。
另外,因為大部分負載都在客戶端上運行,所以服務器在大部分時間里都處于空閑狀態。對服務器資源的低需求顯著減少了服務器上的壓力,也潛在地降低了服務器成本。
Angular 2 的另一個優勢是,它可以幫助 SPA 有效地使用微服務。
“ 通常,90–95% 的 SPA 代碼在瀏覽器中運行;當用戶需要新數據或必須執行身份驗證等安全操作時,就會在服務器中運行剩余代碼。 ”
Angular 2 概念
Angular 2 中的關鍵概念包括:
- 模塊
- 組件
- 服務
- 路由
從現在開始,我將 Angular 2 簡稱為 Angular。
模塊
Angular 應用程序是模塊化的。每個 Angular 應用程序擁有至少一個 模塊 ,即根模塊,傳統上將其命名為 AppModule 。根模塊可以是小型應用程序中唯一的模塊,但大多數應用程序都擁有更多的模塊。作為開發人員,您需要決定如何使用模塊概念。通常,會將主要功能或特性映射到一個模塊。假設您的系統中有 5 個主要區域。除了根模塊之外,每個區域都將擁有自己的模塊,總計 6 個模塊。
組件
組件 控制頁面的一片區域,該區域被稱為 視圖 。您定義組件的應用程序邏輯 — 它如何支持類中的視圖。類通過屬性和方法的 API 與視圖交互。一個組件包含一個類、一個模板和元數據。模板是一個 HTML 表單,用于告訴 Angular 如何呈現組件。一個組件只能屬于一個模塊。
服務
服務 提供應用程序所需的任何值、功能或特性。服務通常是一個具有明確定義的較小用途的類;它應高效地執行具體的操作。組件大量使用服務。服務大量使用微服務。
路由
路由 支持在用戶執行應用程序任務時,從一個視圖導航到下一個視圖。路由等效于用于控制菜單和子菜單的機制。
了解 SPA 的優勢并掌握 Angular 概念后,是時候在示例項目上開始實踐了。
需要做的準備工作
要完成示例項目,您需要在開發 PC 上安裝 Node.js 和 Angular CLI (一個用于 Angular 的命令行接口):
- 安裝 Node.js:
- 下載 適合您的系統的版本,選擇默認選項來完成安裝。
- 從操作系統命令行運行 node -v 來驗證版本號 — 在我的情況下,版本為 v6.9.1 。
- 安裝 Angular CLI:
- 運行 npm install -g angular-cli@1.0.0-beta.21 來安裝我用于示例應用程序的版本(在編寫本文時,仍處于公測階段)。(如果您想嘗試一個不同的編譯版,可訪問 CLI 站點 。)需要花約 10 分鐘才能完成安裝。
- 成功完成安裝后,在操作系統命令行上鍵入 ng -v ,以查看您的 CLI 版本號 — 在我的操作系統下:
angular-cli: 1.0.0-beta.21 node: 6.9.1 os: win32 x64
package.json 文件
package.json 文件(Angular 應用程序中一個關鍵的元數據文件)包含應用程序和它的依賴包的細節。此文件是 Angular 應用程序中最重要的文件,尤其是在將代碼從一臺計算機遷移到另一臺時,或者是在升級期間。package.json 文件包含需要安裝的包版本。
您無需為適合本教程練習的 package.json 感到擔心。但在開發生產級應用程序時,必須多多留意此文件中列出的版本號。
以下是來自一個 package.json 文件的一些有效語句:
{ "dependencies" :
{ "foo" : "1.0.0 - 2.9999.9999"
, "bar" : ">=1.0.2 <2.1.2"
, "baz" : ">1.0.2 <=2.3.4"
, "boo" : "2.0.1"
, "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
, "til" : "^1.2"
, "elf" : "~1.2.3"
, "two" : "2.x"
, "thr" : "3.3.x"
, "lat" : "latest"
}
}
npm install 命令對冒號右側的部分語句的解釋如下(其中 版本 表示所使用的版本號):
- " 版本 ":必須與 版本 準確匹配
- "> 版本 ":必須大于 版本
- "~ 版本 ":約等于 版本
- "^ 版本 ":兼容 版本
- "1.2.x":1.2.0、1.2.1 等,但不能是 1.3.0
- "*" :匹配任何 版本
- ""(空字符串)":與 * 相同
- " 版本 1 - 版本 2 ":與 ">= 版本 1 <= 版本 2 " 相同
要進一步了解 package.json,可在操作系統命令行上鍵入 npm help package.json ,以便在瀏覽器中查看幫助內容。
示例項目概述
示例項目包含一個開箱即用的 Angular 應用程序,以及您將在開箱即用的應用程序上開發的一個自定義應用程序。當您完成上述操作后,您將擁有一個包含 3 個微型應用程序的 Angular 應用程序,每個微型應用程序中的特性使用了 3 個 Web 服務 API:
- 來自雅虎的天氣組件
- 貨幣兌換
- 電影細節
所有應用程序邏輯都將在您的瀏覽器中運行。僅在瀏覽器需要新數據時,才需要服務器。事實上,您可以關閉服務器進程,它仍會在您的應用程序中工作,因為它是一個 SPA。
下圖顯示了應用程序拓撲結構:
您將使用 Angular CLI 創建您的項目(該項目默認情況下會包含 AppModule 和 AppComponent )和 4 個自定義組件:
- 菜單組件
- 天氣組件
- 電影組件
- 貨幣組件
您將創建用于菜單導航的路由,并將以下服務注入到天氣、電影和貨幣組件中:
- 來自使用微服務的 HTTP 的數據
- 在使用這些服務時跨組件的資源共享
創建基礎應用程序和模塊
準備好開始了嗎?首先在操作系統命令行上轉到您想放置項目目錄的位置。
創建一個 Angular 項目
運行下面的命令來生成一個新 Angular 項目(其中 dw_ng2_app 是項目名稱):
ng new dw_ng2_app --skip-git
安裝所有需要的包和 Angular 基礎應用程序(這將花費大約 10 分鐘時間)后,您將返回到操作系統命令提示符上。如果您隨后列出 /dw_ng2_app 目錄的內容,就可以看到項目結構:
|— e2e
|— node_modules
|— src
angular-cli.json
karma.conf.js
package.json
protractor.conf.js
README.md
tslint.json
../dw_ng2_app/src 目錄的內容包括:
|— app
|— assets
|— environments
favicon.ico
index.html
main.ts
polyfills.ts
styles.css
test.ts
tsconfig.json
typings.d.ts
../dw_ng2_app/src/app 目錄( 根模塊文件夾 )包含以下文件:
app.component.css
app.component.html
app.component.spec.ts
app.component.ts
app.module.ts
index.ts
運行開箱即用的 Angular 應用程序
更改到項目目錄,運行 ng serve 來啟動開箱即用的 Angular 應用程序。
默認情況下,該進程在端口 4200 上啟動。如果您的 port 系統環境變量的值不是 4200,該進程將在此端口上啟動。您可以運行 ng serve --port 4200 命令來覆蓋默認端口號,這是一項可選操作。
打開您的瀏覽器并輸入 URL http://localhost:4200/ 。您的 Angular 應用程序會顯示 app works! ,這表明應用程序已啟動、運行并準備就緒:
如果在應用程序運行過程中更改代碼,Angular 會非常智能地監視并自動重新啟動應用程序。嘗試編輯 app.component.ts 文件,更改 title 的值。您可以看到,您的瀏覽器頁面反映了這一更改:
如何將模塊鏈接到組件
在清單 1 中,第 20 行顯示了 AppModule 模塊的聲明。
清單 1. app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
此模塊僅包含一個組件 — AppComponent — 如上第 10 行所示。第 18 行表明,在引導進程下啟動的第一個組件是 AppComponent 。
組件內部結構
清單 2 給出了 app.component.ts 文件中的主要應用程序組件的內容。
清單 2. app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'My Angular 2 app works!';
}
這是 app.component.html 文件中的主要應用程序組件的內容:
<h1>
{{title}}
</h1>
元數據位置
元數據告訴 Angular 如何處理類。事實上,在您通過向 AppComponent 附加元數據給類時,告訴 Angular 它還不是組件。
可以使用 @Component 修飾器附加元數據,這會將類標識為組件。 @Component 修飾器接受一個必需的配置對象,該對象包含 Angular 創建和呈現該組件和它的視圖所需的信息。
中的代碼使用可用的 @Component 配置選項中的 3 個選項:
- selector :一個 CSS 選擇器,告訴 Angular 在父 HTML 文件中找到 selector 標記的地方創建和插入此組件的一個實例
- templateUrl :組件的 HTML 文件
- styleUrls :組件的樣式表,比如 .css 文件
組件內的模板位置
模板是一個 HTML 表單,用于告訴 Angular 如何呈現組件。在的第 5 行上, templateUrl 指向一個名為 app.component.html 的視圖。
組件內的數據綁定
數據綁定是一個 HTML 表單,用于告訴 Angular 如何呈現組件。在 app.component.ts 文件中, title 的值在類內設置,在 app.component.html 文件中使用。數據綁定可以是單向或雙向的。在本例中,如果您在雙花括號 {{ }} 內提及該變量,則該映射是單向的。值從類傳遞到 HTML 文件。
創建自定義組件和路由
此刻,您的 Angular 應用程序已準備就緒并能工作正常。這個基礎應用程序有一個模塊、一個組件、一個類、一個模板、元數據和數據綁定 — 但它仍缺少 4 個其他的重要部分:
- 多個組件
- 路由
- 服務
- 微服務的使用
接下來,您將創建這些自定義組件。
創建自定義組件
按 Ctrl-C 停止 Angular 進程(確保您在 Angular 項目的目錄中,在本例中為 dw_ng2_app)。在命令提示符下,運行以下命令:
- ng g c Menu -is --spec false --flat :在 AppModule 根模塊(同一個文件夾中)內創建 Menu 組件。
- ng g c Weather -is --spec false :在 AppModule 根模塊內名為 weather 的子文件夾中創建 Weather 組件。
- ng g c Currency -is --spec false :在 AppModule 根模塊內名為 currency 的子文件夾中創建 Currency 組件。
- ng g c Movie -is --spec false :在 AppModule 根模塊內名為 movie 的子文件夾中創建 Movie 組件。
現在,有了創建的新組件 — 包括類、元數據和模板 — 您可以看到 AppModule 如何鏈接到這些組件。在清單 3 中,第 28 行包含 AppModule 模塊的聲明。此模塊包含 5 個組件,包括根組件和其他 4 個組件,如第 14–18 行所示。
清單 3. app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { MenuComponent } from './menu.component';
import { WeatherComponent } from './weather/weather.component';
import { CurrencyComponent } from './currency/currency.component';
import { MovieComponent } from './movie/movie.component';
@NgModule({
declarations: [
AppComponent,
MenuComponent,
WeatherComponent,
CurrencyComponent,
MovieComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
創建路由
路由創建命令
我在這里提供了手動創建路由的操作說明。截至編寫本文時,創建路由的 CLI 命令正在開發之中。您可以查閱 CLI 網站 ,看看它現在是否可用。
要讓 Angular 能在組件之間導航,需要創建 路由 。使用清單 4 的內容覆蓋 menu.component.html 文件,以便 HTML 包含所有組件的正確菜單。
清單 4. menu.component.html
<div class="row">
<div class="col-xs-12">
<ul class="nav nav-pills">
<li routerLinkActive="active"> <a [routerLink]="['/weather']" >Weather</a></li>
<li routerLinkActive="active"> <a [routerLink]="['/movie']" >Movie Details</a></li>
<li routerLinkActive="active"> <a [routerLink]="['/currency']" >Currency Rates</a></li>
</ul>
</div>
</div>
清單 4 中的代碼提供了 GUI 與 URL 路徑之間的映射。例如,當用戶單擊 GUI 中的 Movie Details 按鈕時,Angular 知道它需要像 URL 路徑為 http://localhost:4200/movie 一樣運行。
接下來,將 URL 路徑映射到組件。在根模塊的相同文件夾中,創建一個名為 app.routing.ts 的配置文件,使用清單 5 中的代碼作為其內容。
清單 5. app.routing.ts
import { Routes, RouterModule } from '@angular/router';
import { CurrencyComponent } from "./currency/currency.component";
import { WeatherComponent } from "./weather/weather.component";
import { MovieComponent } from "./movie/movie.component";
const MAINMENU_ROUTES: Routes = [
//full : makes sure the path is absolute path
{ path: '', redirectTo: '/weather', pathMatch: 'full' },
{ path: 'weather', component: WeatherComponent },
{ path: 'movie', component: MovieComponent },
{ path: 'currency', component: CurrencyComponent }
];
export const CONST_ROUTING = RouterModule.forRoot(MAINMENU_ROUTES);
在本例中,如果您的 URL 相對路徑是 movie ,則會告訴 Angular 調用 MovieComponent 組件。換句話說,相對路徑 movie 映射到 URL http://localhost:4200/movie 。
現在,您需要將此視圖鏈接到它的父組件。使用以下代碼覆蓋 app.component.html 文件內容:
<div class="container">
<app-menu></app-menu>
<hr>
<router-outlet></router-outlet>
</div>
<app-menu></app-menu> 選擇器會包含菜單。 <router-outlet></router-outlet> 選擇器是當前組件的占位符。根據 URL 路徑,該值可以是以下 3 個組件中的任意一個:天氣、電影或貨幣。
您還必須向該模塊告知此路由。在 app.module.ts 文件中添加兩項,如清單 6 中的第 11 和 25 行所示。
清單 6. app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { MenuComponent } from './menu.component';
import { WeatherComponent } from './weather/weather.component';
import { CurrencyComponent } from './currency/currency.component';
import { MovieComponent } from './movie/movie.component';
import { CONST_ROUTING } from './app.routing';
@NgModule({
declarations: [
AppComponent,
MenuComponent,
WeatherComponent,
CurrencyComponent,
MovieComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
CONST_ROUTING
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
現在,如果您運行應用程序并單擊 Weather 鏈接,應用程序將會顯示 weather works! :
如果單擊 Movie Details 鏈接,應用程序會顯示 movie works! :
如果單擊 Currency Rates 鏈接,應用程序將會顯示 currency works! :
您已成功修改了您的 Angular 應用程序,以便包含多個自定義組件和路由。現在您已經準備好執行最后兩個重要操作:
- 創建和配置服務
- 使用微服務
創建服務
按下 Ctrl-C 停止 Angular 進程。運行下面的命令:
ng g service Shared --spec false
此命令在根模塊文件夾中的 shared.service.ts 文件中創建該服務:
將 shared.service.ts 的內容替換為清單 7 中的代碼。
清單 7. shared.service.ts
import { Injectable } from '@angular/core';
import { Http, Headers, Response } from "@angular/http";
import 'rxjs/Rx';
import { Observable } from "rxjs";
@Injectable()
export class SharedService {
weatherURL1 = "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22";
weatherURL2 = "%2C%20";
weatherURL3 = "%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys";
findMovieURL1 = "http://www.omdbapi.com/?t=";
findMovieURL2 = "&y=&plot=short&r=json";
currencyURL = "http://api.fixer.io/latest?symbols=";
totReqsMade: number = 0;
constructor(private _http: Http) { }
findWeather(city, state) {
this.totReqsMade = this.totReqsMade + 1;
return this._http.get(this.weatherURL1 + city + this.weatherURL2+ state + this.weatherURL3)
.map(response => {
{ return response.json() };
})
.catch(error => Observable.throw(error.json()));
}
findMovie(movie) {
this.totReqsMade = this.totReqsMade + 1;
return this._http.get(this.findMovieURL1 + movie + this.findMovieURL2)
.map(response => {
{ return response.json() };
})
.catch(error => Observable.throw(error.json().error));
}
getCurrencyExchRate(currency) {
this.totReqsMade = this.totReqsMade + 1;
return this._http.get(this.currencyURL + currency)
.map(response => {
{ return response.json() };
})
.catch(error => Observable.throw(error.json()));
}
}
清單 7 中的 import ... 語句是任何服務正常運行所必不可少的。 @Injectable() 語句特別重要;它表明此 service 可注入到其他組件中 — 該技術通常被稱為 依賴注入 。
totReqsMade 變量在這里聲明,將用于在 3 個組件之間傳遞它的值。這將跟蹤為獲得微服務結果而發出的服務請求總數。
您有 3 個方法,它們的名稱表明了自己的功能: findWeather() 、 findMovie() 和 getCurrencyExchRate() 。在方法執行期間,您的 Angular 應用程序將讓瀏覽器訪問網絡來使用微服務。現在您將把組件鏈接到所創建的服務。
將 movie.component.ts 文件內容替換為清單 8 中的代碼。
清單 8. movie.component.ts
import { Component, OnInit } from '@angular/core';
import { SharedService } from "./../shared.service";
@Component({
selector: 'app-movie',
templateUrl: './movie.component.html',
styles: []
})
export class MovieComponent implements OnInit {
id_movie: string = "";
mv_Title: string = "";
mv_Rated: string = "";
mv_Released: string = "";
mv_Director: string = "";
mv_Actors: string = "";
mv_Plot: string = "";
constructor(private _sharedService: SharedService) {
}
ngOnInit() {
}
callMovieService() {
this._sharedService.findMovie(this.id_movie)
.subscribe(
lstresult => {
this.mv_Title = lstresult["Title"];
this.mv_Rated = lstresult["Rated"];
this.mv_Released = lstresult["Released"];
this.mv_Director = lstresult["Director"];
this.mv_Actors = lstresult["Actors"];
this.mv_Plot = lstresult["Plot"];
},
error => {
console.log("Error. The findMovie result JSON value is as follows:");
console.log(error);
}
);
}
}
這個重要的代碼端調用服務方法來獲取新數據。在本例中,它調用 callMovieService() ,然后調用 this._sharedService.findMovie() 方法。
類似地,將 currency.component.ts 文件內容替換為清單 9 中的代碼。
清單 9. currency.component.ts
import { Component, OnInit } from '@angular/core';
import { SharedService } from "./../shared.service";
@Component({
selector: 'app-currency',
templateUrl: './currency.component.html',
styles: []
})
export class CurrencyComponent implements OnInit {
id_currency: string = "";
my_result: any;
constructor(private _sharedService: SharedService) {
}
ngOnInit() {
}
callCurrencyService() {
this._sharedService.getCurrencyExchRate(this.id_currency.toUpperCase())
.subscribe(
lstresult => {
this.my_result = JSON.stringify(lstresult);
},
error => {
console.log("Error. The callCurrencyService result JSON value is as follows:");
console.log(error);
}
);
}
}
將 weather.component.ts 文件內容替換為清單 10 中的代碼。
清單 10. weather.component.ts
import { Component, OnInit } from '@angular/core';
import { SharedService } from "./../shared.service";
@Component({
selector: 'app-weather',
templateUrl: './weather.component.html',
styles: []
})
export class WeatherComponent implements OnInit {
id_city: string = "";
id_state: string = "";
op_city: string = "";
op_region: string = "";
op_country: string = "";
op_date: string = "";
op_text: string = "";
op_temp: string = "";
constructor(private _sharedService: SharedService) {
}
ngOnInit() {
}
callWeatherService() {
this._sharedService.findWeather(this.id_city, this.id_state)
.subscribe(
lstresult => {
this.op_city = lstresult["query"]["results"]["channel"]["location"]["city"];
this.op_region = lstresult["query"]["results"]["channel"]["location"]["region"];
this.op_country = lstresult["query"]["results"]["channel"]["location"]["country"];
this.op_date = lstresult["query"]["results"]["channel"]["item"]["condition"]["date"];
this.op_text = lstresult["query"]["results"]["channel"]["item"]["condition"]["text"];
this.op_temp = lstresult["query"]["results"]["channel"]["item"]["condition"]["temp"];
},
error => {
console.log("Error. The findWeather result JSON value is as follows:");
console.log(error);
}
);
}
}
現在,更新該模塊以包含這些服務。編輯 app.module.ts 文件,以包含清單 11 的第 12 和 28 行的兩條語句。
清單 11. app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { MenuComponent } from './menu.component';
import { WeatherComponent } from './weather/weather.component';
import { CurrencyComponent } from './currency/currency.component';
import { MovieComponent } from './movie/movie.component';
import { CONST_ROUTING } from './app.routing';
import { SharedService } from "./shared.service";
@NgModule({
declarations: [
AppComponent,
MenuComponent,
WeatherComponent,
CurrencyComponent,
MovieComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
CONST_ROUTING
],
providers: [SharedService],
bootstrap: [AppComponent]
})
export class AppModule { }
修改組件視圖
現在還剩難題的最后一部分。您需要告訴 HTML 文件調用正確的服務方法。為此,請將 movie.component.html 文件的內容替換為清單 12 中的代碼。
清單 12. movie.component.html
<h2>Open Movie Database</h2>
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<input type="text" required [(ngModel)]="id_movie" (change)="callMovieService()" class="form-control" placeholder="Enter Movie name ...">
<br><br>
<h3>Movie Details</h3>
<br>
<p class="well lead">
<i> Title :</i> {{ this.mv_Title }} <br>
<i> Plot :</i> {{ this.mv_Plot }} <br>
<i> Actors :</i> {{ this.mv_Actors }} <br>
<i> Directed by :</i> {{ this.mv_Director }} <br>
<i> Rated :</i> {{ this.mv_Rated }} <br>
<i> Release Date :</i> {{ this.mv_Released }} <br>
</p>
<p class="text-info">Total # of all the service requests including Weather, Movie, and Currency is :
<span class="badge">{{this._sharedService.totReqsMade}}</span>
</p>
</div>
</div>
movie.component.html 中編碼了一些重要的信息:
- {{ this._sharedService.totReqsMade }} :這是在服務級別上跟蹤的值,它會在所有 3 個應用程序組件之間共享。
- [(ngModel)]="id_movie" :用戶輸入的 GUI 輸入被傳遞到調用此 HTML 的類。在本例中,該類為 MovieComponent 。
- (change)="callMovieService() ":當此字段值更改時,就會告訴系統調用 movie.component.ts 文件中包含的 callMovieService() 方法。
- {{ this.mv_Title }}, {{ this.mv_Plot }}, {{ this.mv_Actors }}, {{ this.mv_Director }}, {{ this.mv_Rated }}, {{ this.mv_Released }} :顯示從 callMovieService() -> this._sharedService.findMovie(this.id_movie) 執行的服務調用的結果。
將 weather.component.html 文件的內容替換為清單 13 中的代碼。
清單 13. weather.component.html
<h2>Yahoo! Weather </h2>
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<input type="text" [(ngModel)]="id_city" class="form-control" placeholder="Enter City name ..."><br>
<input type="text" [(ngModel)]="id_state" class="form-control" placeholder="Enter State. Example CA for California ..."><br>
<button type="button" class="btn btn-primary" (click)="callWeatherService()">Submit</button>
<br><br><br>
<br>
<p class="well lead">
<i>City, State, Country :</i> {{ this.op_city }} {{ this.op_region }} {{ this.op_country }} <br>
<i>Current Condition :</i> {{ this.op_text }} <br>
<i>Current Temperature :</i> {{ this.op_temp }} <br>
</p>
<p class="text-info">Total # of all the service requests including Weather, Movie, and Currency is :
<span class="badge">{{this._sharedService.totReqsMade}}</span>
</p>
</div>
</div>
最后,將 currency.component.html 文件的內容替換為清單 14 中的內容。
清單 14. currency.component.html
<h2>Currency Exchange Rates</h2>
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<input type="text" [(ngModel)]="id_currency" (change)="callCurrencyService()" class="form-control" placeholder="Enter Currency Symbol. Example: GBP(,AUD,INR)...">
<br><br>
<h3>Rate Details</h3>
<br>
<p class="well lead">Exchange rate relative to Euro in a JSON format: : {{ this.my_result }} </p>
<p class="text-info">Total # of all the service requests including Weather, Movie, and Currency is :
<span class="badge">{{this._sharedService.totReqsMade}}</span>
</p>
</div>
</div>
現在,如果各部分均按預期運行,應用程序可接受瀏覽器中的用戶輸入。
運行應用程序并改進 UI
現在運行應用程序,輸入一些值,然后查看結果。例如,單擊 Weather 鏈接,輸入 San Francisco 來查看該城市的天氣條件:
測試您的新 Angular 技能
目前,您的應用程序的輸入字段沒有實現驗證或錯誤處理。您可以自行嘗試添加這些功能。提示:在服務中添加方法 validateMovie(movie-name) 、 validateCurrency(currency-name) 、 validateCity(city-name) 和 validateState(state-name) 。然后從相應的組件調用這些方法。
一切正常,但還可以讓 UI 更具吸引力。改進 GUI 的一種方法是使用 Bootstrap 。( Angular 2 資料 是理想的選擇,但截至編寫本文時,它尚未正式發布)。
轉到 Bootstrap 入門頁面 ,將該頁的以下兩行復制到剪貼板:
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
打開 index.html 文件,將剛復制的語句粘貼到第 8 行下。
清單 15. index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>DwNg2App</title>
<base href="/">
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
現在該應用程序在瀏覽器中看起來更加美觀,擁有更容易理解的樣式和菜單按鈕,而不是 Weather、Movie Details 和 Currency Rates 鏈接:
嵌套的 index.html
現在花一分鐘時間體會一下您剛完成的應用程序為什么被稱為 SPA。
當 Angular 應用程序啟動時,服務器將 index.html 文件發送到瀏覽器,而且 index.html 是瀏覽器顯示的唯一文件。Angular 對該頁面執行的任何操作都會插入到此視圖中:
index.html 末尾的 <app-root> 選擇器被 app.component.html 的內容所取代。app.component.html 包含兩個選擇器: <app-menu> 和 <router-outlet> 。 <app-menu> 選擇器中填入 menu.component.html 的內容, <router-outlet> 依據菜單選項而動態填充 — 也就是說,填入 weather.component.html、currency.component.html 或 movie.component.html 的內容。
除了 Angular 保留選擇器 <router-outlet></router-outlet> 外,所有選擇器都是靜態的。此選擇器在運行時期間依據路由器值而填充。僅顯示 index.html,您編碼的其他所有 HTML 文件都嵌套在 index.html 文件內。
模擬服務器
您的 Angular 項目已成功在開發計算機中運行。如果您能訪問遠程沙箱服務器,那么可以將代碼遷移到那里,看看應用程序在被用戶運行時的行為。(否則,可以跳到教程的部分。)
確保 Node.js 和 Angular CLI 已安裝在遠程沙箱中。壓縮本地項目文件夾中的所有內容,node_modules 目錄及其內容除外。將壓縮后的項目文件復制到沙箱服務器并解壓。轉到包含 package.json 的服務器目錄并運行 npm install 命令。package.json 文件使 npm install 命令能轉到 NPM 公共存儲庫,安裝所有需要的包版本。運行此命令也會自動在服務器上創建 node_modules 目錄和它的內容。
運行 ng serve 命令,以便在沙箱服務器中啟動該應用程序,就像在開發計算機中所做的一樣。按下 Ctrl-C 停止該進程。同樣地,如果您想了解 ng serve 的其他選項,可以運行 ng help 命令。
使用 ng serve --port sandbox-port# --host sandbox-hostname 命令運行該應用程序。
現在 Angular 應用程序可在 URL http:// sandbox-hostname : sandbox-port# 上訪問。在開發計算機瀏覽器中運行該 URL 上的應用程序時,在沙箱服務器上按下 Ctrl-C 停止服務器 Angular 進程。請注意,整個應用程序都在開發計算機的瀏覽器中運行,盡管服務器 Angular 進程已關閉。這會告訴您,SPA 技術正在運行。該應用程序加載到瀏覽器中后,除非應用程序需要新數據,否則控制權絕不會轉交給服務器。
在 SPA 領域,瀏覽器就是新型的服務器。如果 10 個用戶在運行該應用程序,就會有 10 個瀏覽器處理該負載。如果 1,000 個用戶在運行該應用程序,則有 1,000 個瀏覽器處理該負載。整個 Angular 應用程序都在瀏覽器的控制之下,除了在服務器中運行的任何邏輯,比如身份驗證和數據庫操作。
更高的性能和服務器壓力的減輕,最終會提高用戶體驗和用戶滿意度 — 這是任何企業獲得成功的關鍵。
“ 在 SPA 領域,瀏覽器就是新型的服務器。 ”
結束語
您學習了如何使用 Angular 2 編寫一個 SPA,并在開發計算機和沙箱服務器中運行它。對于生產需求,請與您的 IT 部門協商。大多數生產應用程序擁有的一項主要功能是身份驗證和授權,應在服務器中運行該功能(主要出于安全原因)。您可能需要一個專用服務器來處理這些操作。對于該服務器,可以使用 Node.js,在 Angular 2 在前端運行時,它可以充當在后端運行的服務器。(Node 和 Angular 都起源于 Google,所以它們能有效地協同運行。)
您可以考慮的用來提高應用程序性能的其他技術包括:
- 捆綁:該進程將您的許多程序組合到一個文件中。
- 微型化:壓縮捆綁的文件,以便盡可能地減小項目大小。
- 提前 (AoT) 編譯:服務器復雜在構建過程中提前編譯,而不是瀏覽器在運行時期間執行即時 (JIT) 編譯。
來自:http://www.ibm.com/developerworks/cn/web/wa-implement-a-single-page-application-with-angular2/index.html?ca=drs-