使用 Angular 2 实现单页应用程序
<p>由于具有实现更高性能的浏览器和智能电话应用程序的潜力,单页应用程序 (SPA) 技术在软件行业引起了广泛的兴趣。在过去 5 年多的时间里,开发人员对 Angular (一个开源 SPA 框架)的兴趣远超他们对其他 Web 框架(React、Backbone、Meteor 和 Ember)的兴趣,这从 StackOverflow 网站上针对每种 Web 框架的问题数量上可以判断出:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/a381b1f25e62e8329c7db1ead2146f5f.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6ea4d10788783a463a2135bbe6884ded.jpg" alt="使用 Angular 2 实现单页应用程序" width="550" height="357"></p> <p>Web 和移动开发人员非常喜欢 Angular 2(2016 年 9 月发布)。Angular 2 不是 Angular 1 的一次升级,而是一个全新的、不同的、更高级的框架。精通 Angular 2 已成为构建高性能、可扩展、稳健、现代的跨平台应用程序的一种很吃香的技能。</p> <p>TypeScript</p> <p>Angular 2 支持 JavaScript、Dart 和 TypeScript。您将使用 TypeScript 作为本教程的项目的开发语言。该框架构建于 TypeScript 之上,大多数有关 Angular 的文档、图书和教程都侧重于将 TypeScript 作为开发语言。</p> <p>本教程将介绍 Angular 2 的重要构建基块,演示如何在开发计算机和沙箱服务器中使用 Angular 2 编写和运行 SPA。要充分掌握本教程,您需要具有一定的 Web 编程经验,包括 JavaScript、TypeScript 和 HTML 的各方面知识。无需提前拥有 Angular 经验。练习完示例项目后,您就会为使用 Angular 2 创建自己的 SPA 做好充足的准备。</p> <p>可以从教程末尾的 “可下载资源” 部分下载针对沙箱应用程序的完整示例代码。</p> <h2>为什么使用 SPA 和 Angular 2?</h2> <p>在用户启动 SPA 时,该应用程序仅呈现来自服务器的一个 HTML 页面。除了这个 HTML 页面,服务器还会向客户端发送一个应用程序引擎。该引擎控制整个应用程序,包括HTML 页面的处理、输入、输出、绘制和加载。通常,90–95% 的应用程序代码是在浏览器中运行;当用户需要新数据时或必须执行身份验证等安全操作时,就会在服务器中运行剩余代码。由于几乎消除了对服务器的依赖,所以 SPA 能在 Angular 2 环境中自动扩展:无论有多少用户同时访问服务器,在 90–95% 的时间里,应用程序的性能绝不会受到影响。</p> <p>另外,因为大部分负载都在客户端上运行,所以服务器在大部分时间里都处于空闲状态。对服务器资源的低需求显著减少了服务器上的压力,也潜在地降低了服务器成本。</p> <p>Angular 2 的另一个优势是,它可以帮助 SPA 有效地使用微服务。</p> <p>“ 通常,90–95% 的 SPA 代码在浏览器中运行;当用户需要新数据或必须执行身份验证等安全操作时,就会在服务器中运行剩余代码。 ”</p> <h2>Angular 2 概念</h2> <p>Angular 2 中的关键概念包括:</p> <ul> <li>模块</li> <li>组件</li> <li>服务</li> <li>路由</li> </ul> <p>从现在开始,我将 Angular 2 简称为 Angular。</p> <h3>模块</h3> <p>Angular 应用程序是模块化的。每个 Angular 应用程序拥有至少一个 <em>模块</em> ,即根模块,传统上将其命名为 AppModule 。根模块可以是小型应用程序中唯一的模块,但大多数应用程序都拥有更多的模块。作为开发人员,您需要决定如何使用模块概念。通常,会将主要功能或特性映射到一个模块。假设您的系统中有 5 个主要区域。除了根模块之外,每个区域都将拥有自己的模块,总计 6 个模块。</p> <h3>组件</h3> <p><em>组件</em> 控制页面的一片区域,该区域被称为 <em>视图</em> 。您定义组件的应用程序逻辑 — 它如何支持类中的视图。类通过属性和方法的 API 与视图交互。一个组件包含一个类、一个模板和元数据。模板是一个 HTML 表单,用于告诉 Angular 如何呈现组件。一个组件只能属于一个模块。</p> <h3>服务</h3> <p><em>服务</em> 提供应用程序所需的任何值、功能或特性。服务通常是一个具有明确定义的较小用途的类;它应高效地执行具体的操作。组件大量使用服务。服务大量使用微服务。</p> <h3>路由</h3> <p><em>路由</em> 支持在用户执行应用程序任务时,从一个视图导航到下一个视图。路由等效于用于控制菜单和子菜单的机制。</p> <p>了解 SPA 的优势并掌握 Angular 概念后,是时候在示例项目上开始实践了。</p> <h2>需要做的准备工作</h2> <p>要完成示例项目,您需要在开发 PC 上安装 Node.js 和 Angular CLI (一个用于 Angular 的命令行接口):</p> <ul> <li>安装 Node.js: <ol> <li>下载 适合您的系统的版本,选择默认选项来完成安装。</li> <li>从操作系统命令行运行 node -v 来验证版本号 — 在我的情况下,版本为 v6.9.1 。</li> </ol> Node Package Manager (NPM) 会随 Node 自动安装。键入 npm -v 来查看它的版本号。当您安装来自公共 NPM 存储库的包时,会在后台运行 NPM。一个常见的 NPM 命令是 npm install ,它用于下载您的 Angular 项目的文件中列出的包版本。</li> <li>安装 Angular CLI: <ol> <li>运行 npm install -g angular-cli@1.0.0-beta.21 来安装我用于示例应用程序的版本(在编写本文时,仍处于公测阶段)。(如果您想尝试一个不同的编译版,可访问 CLI 站点 。)需要花约 10 分钟才能完成安装。</li> <li>成功完成安装后,在操作系统命令行上键入 ng -v ,以查看您的 CLI 版本号 — 在我的操作系统下: <pre> <code class="language-javascript">angular-cli: 1.0.0-beta.21 node: 6.9.1 os: win32 x64</code></pre> 如果您需要获得有关 ng 语法的帮助,可在操作系统命令行上键入 ng help 。</li> </ol> </li> </ul> <h2>package.json 文件</h2> <p>package.json 文件(Angular 应用程序中一个关键的元数据文件)包含应用程序和它的依赖包的细节。此文件是 Angular 应用程序中最重要的文件,尤其是在将代码从一台计算机迁移到另一台时,或者是在升级期间。package.json 文件包含需要安装的包版本。</p> <p>您无需为适合本教程练习的 package.json 感到担心。但在开发生产级应用程序时,必须多多留意此文件中列出的版本号。</p> <p>以下是来自一个 package.json 文件的一些有效语句:</p> <pre> <code class="language-javascript">{ "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" } }</code></pre> <p>npm install 命令对冒号右侧的部分语句的解释如下(其中 <em>版本</em> 表示所使用的版本号):</p> <ul> <li>" <em>版本</em> ":必须与 <em>版本</em> 准确匹配</li> <li>"> <em>版本</em> ":必须大于 <em>版本</em></li> <li>"~ <em>版本</em> ":约等于 <em>版本</em></li> <li>"^ <em>版本</em> ":兼容 <em>版本</em></li> <li>"1.2.x":1.2.0、1.2.1 等,但不能是 1.3.0</li> <li>"*" :匹配任何 <em>版本</em></li> <li>""(空字符串)":与 * 相同</li> <li>" <em>版本 1</em> - <em>版本 2</em> ":与 ">= <em>版本 1</em> <= <em>版本 2</em> " 相同</li> </ul> <p>要进一步了解 package.json,可在操作系统命令行上键入 npm help package.json ,以便在浏览器中查看帮助内容。</p> <h2>示例项目概述</h2> <p>示例项目包含一个开箱即用的 Angular 应用程序,以及您将在开箱即用的应用程序上开发的一个自定义应用程序。当您完成上述操作后,您将拥有一个包含 3 个微型应用程序的 Angular 应用程序,每个微型应用程序中的特性使用了 3 个 Web 服务 API:</p> <ul> <li>来自雅虎的天气组件</li> <li>货币兑换</li> <li>电影细节</li> </ul> <p>所有应用程序逻辑都将在您的浏览器中运行。仅在浏览器需要新数据时,才需要服务器。事实上,您可以关闭服务器进程,它仍会在您的应用程序中工作,因为它是一个 SPA。</p> <p>下图显示了应用程序拓扑结构:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/03ebf60f06cc43ffaddc69d82e221a34.png"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/fe06f8b5e0c0669db8db884db08d9b44.png" alt="使用 Angular 2 实现单页应用程序" width="550" height="232"></p> <p>您将使用 Angular CLI 创建您的项目(该项目默认情况下会包含 AppModule 和 AppComponent )和 4 个自定义组件:</p> <ul> <li>菜单组件</li> <li>天气组件</li> <li>电影组件</li> <li>货币组件</li> </ul> <p>您将创建用于菜单导航的路由,并将以下服务注入到天气、电影和货币组件中:</p> <ul> <li>来自使用微服务的 HTTP 的数据</li> <li>在使用这些服务时跨组件的资源共享</li> </ul> <h2>创建基础应用程序和模块</h2> <p>准备好开始了吗?首先在操作系统命令行上转到您想放置项目目录的位置。</p> <h3>创建一个 Angular 项目</h3> <p>运行下面的命令来生成一个新 Angular 项目(其中 dw_ng2_app 是项目名称):</p> <pre> <code class="language-javascript">ng new dw_ng2_app --skip-git</code></pre> <p>安装所有需要的包和 Angular 基础应用程序(这将花费大约 10 分钟时间)后,您将返回到操作系统命令提示符上。如果您随后列出 /dw_ng2_app 目录的内容,就可以看到项目结构:</p> <pre> <code class="language-javascript">|— e2e |— node_modules |— src angular-cli.json karma.conf.js package.json protractor.conf.js README.md tslint.json</code></pre> <p>../dw_ng2_app/src 目录的内容包括:</p> <pre> <code class="language-javascript">|— app |— assets |— environments favicon.ico index.html main.ts polyfills.ts styles.css test.ts tsconfig.json typings.d.ts</code></pre> <p>../dw_ng2_app/src/app 目录( <em>根模块文件夹</em> )包含以下文件:</p> <pre> <code class="language-javascript">app.component.css app.component.html app.component.spec.ts app.component.ts app.module.ts index.ts</code></pre> <h3>运行开箱即用的 Angular 应用程序</h3> <p>更改到项目目录,运行 ng serve 来启动开箱即用的 Angular 应用程序。</p> <p>默认情况下,该进程在端口 4200 上启动。如果您的 port 系统环境变量的值不是 4200,该进程将在此端口上启动。您可以运行 ng serve --port 4200 命令来覆盖默认端口号,这是一项可选操作。</p> <p>打开您的浏览器并输入 URL http://localhost:4200/ 。您的 Angular 应用程序会显示 <strong>app works!</strong> ,这表明应用程序已启动、运行并准备就绪:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6463d1d74ff25a905cfd127cff56cebb.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/715ccdb616e80ddcf3b57ffa5c057302.jpg" alt="使用 Angular 2 实现单页应用程序" width="550" height="400"></p> <p>如果在应用程序运行过程中更改代码,Angular 会非常智能地监视并自动重新启动应用程序。尝试编辑 app.component.ts 文件,更改 title 的值。您可以看到,您的浏览器页面反映了这一更改:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/7103f10adb87a4755f8bc553f6b963eb.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3c5628369699975f459bb565782e5c03.jpg" alt="使用 Angular 2 实现单页应用程序" width="485" height="400"></p> <h3>如何将模块链接到组件</h3> <p>在清单 1 中,第 20 行显示了 AppModule 模块的声明。</p> <p>清单 1. app.module.ts</p> <pre> <code class="language-javascript">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 { }</code></pre> <p>此模块仅包含一个组件 — AppComponent — 如上第 10 行所示。第 18 行表明,在引导进程下启动的第一个组件是 AppComponent 。</p> <h3>组件内部结构</h3> <p>清单 2 给出了 app.component.ts 文件中的主要应用程序组件的内容。</p> <p>清单 2. app.component.ts</p> <pre> <code class="language-javascript">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!'; }</code></pre> <p>这是 app.component.html 文件中的主要应用程序组件的内容:</p> <pre> <code class="language-javascript"><h1> {{title}} </h1></code></pre> <h3>元数据位置</h3> <p>元数据告诉 Angular 如何处理类。事实上,在您通过向 AppComponent 附加元数据给类时,告诉 Angular 它还不是组件。</p> <p>可以使用 @Component 修饰器附加元数据,这会将类标识为组件。 @Component 修饰器接受一个必需的配置对象,该对象包含 Angular 创建和呈现该组件和它的视图所需的信息。</p> <p>中的代码使用可用的 @Component 配置选项中的 3 个选项:</p> <ul> <li>selector :一个 CSS 选择器,告诉 Angular 在父 HTML 文件中找到 selector 标记的地方创建和插入此组件的一个实例</li> <li>templateUrl :组件的 HTML 文件</li> <li>styleUrls :组件的样式表,比如 .css 文件</li> </ul> <h3>组件内的模板位置</h3> <p>模板是一个 HTML 表单,用于告诉 Angular 如何呈现组件。在的第 5 行上, templateUrl 指向一个名为 app.component.html 的视图。</p> <h3>组件内的数据绑定</h3> <p>数据绑定是一个 HTML 表单,用于告诉 Angular 如何呈现组件。在 app.component.ts 文件中, title 的值在类内设置,在 app.component.html 文件中使用。数据绑定可以是单向或双向的。在本例中,如果您在双花括号 {{ }} 内提及该变量,则该映射是单向的。值从类传递到 HTML 文件。</p> <h2>创建自定义组件和路由</h2> <p>此刻,您的 Angular 应用程序已准备就绪并能工作正常。这个基础应用程序有一个模块、一个组件、一个类、一个模板、元数据和数据绑定 — 但它仍缺少 4 个其他的重要部分:</p> <ul> <li>多个组件</li> <li>路由</li> <li>服务</li> <li>微服务的使用</li> </ul> <p>接下来,您将创建这些自定义组件。</p> <h3>创建自定义组件</h3> <p>按 Ctrl-C 停止 Angular 进程(确保您在 Angular 项目的目录中,在本例中为 dw_ng2_app)。在命令提示符下,运行以下命令:</p> <ul> <li>ng g c Menu -is --spec false --flat :在 AppModule 根模块(同一个文件夹中)内创建 Menu 组件。</li> <li>ng g c Weather -is --spec false :在 AppModule 根模块内名为 weather 的子文件夹中创建 Weather 组件。</li> <li>ng g c Currency -is --spec false :在 AppModule 根模块内名为 currency 的子文件夹中创建 Currency 组件。</li> <li>ng g c Movie -is --spec false :在 AppModule 根模块内名为 movie 的子文件夹中创建 Movie 组件。</li> </ul> <p>现在,有了创建的新组件 — 包括类、元数据和模板 — 您可以看到 AppModule 如何链接到这些组件。在清单 3 中,第 28 行包含 AppModule 模块的声明。此模块包含 5 个组件,包括根组件和其他 4 个组件,如第 14–18 行所示。</p> <p>清单 3. app.module.ts</p> <pre> <code class="language-javascript">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 { }</code></pre> <h3>创建路由</h3> <p>路由创建命令</p> <p>我在这里提供了手动创建路由的操作说明。截至编写本文时,创建路由的 CLI 命令正在开发之中。您可以查阅 <a href="/misc/goto?guid=4959007487269231953" rel="nofollow,noindex">CLI 网站</a> ,看看它现在是否可用。</p> <p>要让 Angular 能在组件之间导航,需要创建 <em>路由</em> 。使用清单 4 的内容覆盖 menu.component.html 文件,以便 HTML 包含所有组件的正确菜单。</p> <p>清单 4. menu.component.html</p> <pre> <code class="language-javascript"><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></code></pre> <p>清单 4 中的代码提供了 GUI 与 URL 路径之间的映射。例如,当用户单击 GUI 中的 Movie Details 按钮时,Angular 知道它需要像 URL 路径为 http://localhost:4200/movie 一样运行。</p> <p>接下来,将 URL 路径映射到组件。在根模块的相同文件夹中,创建一个名为 app.routing.ts 的配置文件,使用清单 5 中的代码作为其内容。</p> <p>清单 5. app.routing.ts</p> <pre> <code class="language-javascript">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);</code></pre> <p>在本例中,如果您的 URL 相对路径是 movie ,则会告诉 Angular 调用 MovieComponent 组件。换句话说,相对路径 movie 映射到 URL http://localhost:4200/movie 。</p> <p>现在,您需要将此视图链接到它的父组件。使用以下代码覆盖 app.component.html 文件内容:</p> <pre> <code class="language-javascript"><div class="container"> <app-menu></app-menu> <hr> <router-outlet></router-outlet> </div></code></pre> <p><app-menu></app-menu> 选择器会包含菜单。 <router-outlet></router-outlet> 选择器是当前组件的占位符。根据 URL 路径,该值可以是以下 3 个组件中的任意一个:天气、电影或货币。</p> <p>您还必须向该模块告知此路由。在 app.module.ts 文件中添加两项,如清单 6 中的第 11 和 25 行所示。</p> <p>清单 6. app.module.ts</p> <pre> <code class="language-javascript">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 { }</code></pre> <p>现在,如果您运行应用程序并单击 <strong>Weather</strong> 链接,应用程序将会显示 <strong>weather works!</strong> :</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9e25bd24f2442b50767d30c0b9ff595f.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6e1124dcb2443e739f61bcb57705b2bb.jpg" alt="使用 Angular 2 实现单页应用程序" width="450" height="312"></p> <p>如果单击 <strong>Movie Details</strong> 链接,应用程序会显示 <strong>movie works!</strong> :</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/8eab4632e3131c2cf8a56d1b872c8ef5.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/10b4a3b626e13831b4c661c0d7a02c67.jpg" alt="使用 Angular 2 实现单页应用程序" width="450" height="312"></p> <p>如果单击 <strong>Currency Rates</strong> 链接,应用程序将会显示 <strong>currency works!</strong> :</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/c76da7453971bb0485dd9623b57fb2b7.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3db0389885261bdbdb4d58a1bc22241d.jpg" alt="使用 Angular 2 实现单页应用程序" width="450" height="312"></p> <p>您已成功修改了您的 Angular 应用程序,以便包含多个自定义组件和路由。现在您已经准备好执行最后两个重要操作:</p> <ul> <li>创建和配置服务</li> <li>使用微服务</li> </ul> <h2>创建服务</h2> <p>按下 Ctrl-C 停止 Angular 进程。运行下面的命令:</p> <pre> <code class="language-javascript">ng g service Shared --spec false</code></pre> <p>此命令在根模块文件夹中的 shared.service.ts 文件中创建该服务:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/92c0efcf74761b8f13c1779cdf5e2cee.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/d20e8103d9be491a5a8b662241442ed4.jpg" alt="使用 Angular 2 实现单页应用程序" width="550" height="123"></p> <p>将 shared.service.ts 的内容替换为清单 7 中的代码。</p> <p>清单 7. shared.service.ts</p> <pre> <code class="language-javascript">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())); } }</code></pre> <p>清单 7 中的 import ... 语句是任何服务正常运行所必不可少的。 @Injectable() 语句特别重要;它表明此 service 可注入到其他组件中 — 该技术通常被称为 <em>依赖注入</em> 。</p> <p>totReqsMade 变量在这里声明,将用于在 3 个组件之间传递它的值。这将跟踪为获得微服务结果而发出的服务请求总数。</p> <p>您有 3 个方法,它们的名称表明了自己的功能: findWeather() 、 findMovie() 和 getCurrencyExchRate() 。在方法执行期间,您的 Angular 应用程序将让浏览器访问网络来使用微服务。现在您将把组件链接到所创建的服务。</p> <p>将 movie.component.ts 文件内容替换为清单 8 中的代码。</p> <p>清单 8. movie.component.ts</p> <pre> <code class="language-javascript">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); } ); } }</code></pre> <p>这个重要的代码端调用服务方法来获取新数据。在本例中,它调用 callMovieService() ,然后调用 this._sharedService.findMovie() 方法。</p> <p>类似地,将 currency.component.ts 文件内容替换为清单 9 中的代码。</p> <p>清单 9. currency.component.ts</p> <pre> <code class="language-javascript">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); } ); } }</code></pre> <p>将 weather.component.ts 文件内容替换为清单 10 中的代码。</p> <p>清单 10. weather.component.ts</p> <pre> <code class="language-javascript">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); } ); } }</code></pre> <p>现在,更新该模块以包含这些服务。编辑 app.module.ts 文件,以包含清单 11 的第 12 和 28 行的两条语句。</p> <p>清单 11. app.module.ts</p> <pre> <code class="language-javascript">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 { }</code></pre> <h2>修改组件视图</h2> <p>现在还剩难题的最后一部分。您需要告诉 HTML 文件调用正确的服务方法。为此,请将 movie.component.html 文件的内容替换为清单 12 中的代码。</p> <p>清单 12. movie.component.html</p> <pre> <code class="language-javascript"><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></code></pre> <p>movie.component.html 中编码了一些重要的信息:</p> <ul> <li>{{ this._sharedService.totReqsMade }} :这是在服务级别上跟踪的值,它会在所有 3 个应用程序组件之间共享。</li> <li>[(ngModel)]="id_movie" :用户输入的 GUI 输入被传递到调用此 HTML 的类。在本例中,该类为 MovieComponent 。</li> <li>(change)="callMovieService() ":当此字段值更改时,就会告诉系统调用 movie.component.ts 文件中包含的 callMovieService() 方法。</li> <li>{{ 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) 执行的服务调用的结果。</li> </ul> <p>将 weather.component.html 文件的内容替换为清单 13 中的代码。</p> <p>清单 13. weather.component.html</p> <pre> <code class="language-javascript"><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></code></pre> <p>最后,将 currency.component.html 文件的内容替换为清单 14 中的内容。</p> <p>清单 14. currency.component.html</p> <pre> <code class="language-javascript"><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></code></pre> <p>现在,如果各部分均按预期运行,应用程序可接受浏览器中的用户输入。</p> <h2>运行应用程序并改进 UI</h2> <p>现在运行应用程序,输入一些值,然后查看结果。例如,单击 Weather 链接,输入 San Francisco 来查看该城市的天气条件:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/38a62f9cf615328016408325af5ef3d1.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/74e533d7365ac7fdff71eb6a4d10b68a.jpg" alt="使用 Angular 2 实现单页应用程序" width="550" height="420"></p> <p>测试您的新 Angular 技能</p> <p>目前,您的应用程序的输入字段没有实现验证或错误处理。您可以自行尝试添加这些功能。提示:在服务中添加方法 <strong>validateMovie(movie-name)</strong> 、 <strong>validateCurrency(currency-name)</strong> 、 <strong>validateCity(city-name)</strong> 和 <strong>validateState(state-name)</strong> 。然后从相应的组件调用这些方法。</p> <p>一切正常,但还可以让 UI 更具吸引力。改进 GUI 的一种方法是使用 Bootstrap 。( Angular 2 资料 是理想的选择,但截至编写本文时,它尚未正式发布)。</p> <p>转到 Bootstrap 入门页面 ,将该页的以下两行复制到剪贴板:</p> <pre> <code class="language-javascript"><!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"></code></pre> <p>打开 index.html 文件,将刚复制的语句粘贴到第 8 行下。</p> <p>清单 15. index.html</p> <pre> <code class="language-javascript"><!doctype html> <html> <head> <meta charset="utf-8"> <title>DwNg2App</title> <base href="/"> <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" 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></code></pre> <p>现在该应用程序在浏览器中看起来更加美观,拥有更容易理解的样式和菜单按钮,而不是 Weather、Movie Details 和 Currency Rates 链接:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9ab97291fe6e773ff647e4fc6f5c2af6.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/ef2b74ce3bccbd469b66f0ed1c91c892.jpg" alt="使用 Angular 2 实现单页应用程序" width="550" height="490"></p> <h2>嵌套的 index.html</h2> <p>现在花一分钟时间体会一下您刚完成的应用程序为什么被称为 SPA。</p> <p>当 Angular 应用程序启动时,服务器将 index.html 文件发送到浏览器,而且 index.html 是浏览器显示的唯一文件。Angular 对该页面执行的任何操作都会插入到此视图中:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/bb240bd7c07ff3b60f825079d9ddfee6.jpg"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/ad5fcf2383bc260d10793150d5224b82.jpg" alt="使用 Angular 2 实现单页应用程序" width="550" height="283"></p> <p>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 的内容。</p> <p>除了 Angular 保留选择器 <router-outlet></router-outlet> 外,所有选择器都是静态的。此选择器在运行时期间依据路由器值而填充。仅显示 index.html,您编码的其他所有 HTML 文件都嵌套在 index.html 文件内。</p> <h2>模拟服务器</h2> <p>您的 Angular 项目已成功在开发计算机中运行。如果您能访问远程沙箱服务器,那么可以将代码迁移到那里,看看应用程序在被用户运行时的行为。(否则,可以跳到教程的部分。)</p> <p>确保 Node.js 和 Angular CLI 已安装在远程沙箱中。压缩本地项目文件夹中的所有内容,node_modules 目录及其内容除外。将压缩后的项目文件复制到沙箱服务器并解压。转到包含 package.json 的服务器目录并运行 npm install 命令。package.json 文件使 npm install 命令能转到 NPM 公共存储库,安装所有需要的包版本。运行此命令也会自动在服务器上创建 node_modules 目录和它的内容。</p> <p>运行 ng serve 命令,以便在沙箱服务器中启动该应用程序,就像在开发计算机中所做的一样。按下 Ctrl-C 停止该进程。同样地,如果您想了解 ng serve 的其他选项,可以运行 ng help 命令。</p> <p>使用 ng serve --port <em>sandbox-port#</em> --host <em>sandbox-hostname</em> 命令运行该应用程序。</p> <p>现在 Angular 应用程序可在 URL http:// <em>sandbox-hostname</em> : <em>sandbox-port#</em> 上访问。在开发计算机浏览器中运行该 URL 上的应用程序时,在沙箱服务器上按下 Ctrl-C 停止服务器 Angular 进程。请注意,整个应用程序都在开发计算机的浏览器中运行,尽管服务器 Angular 进程已关闭。这会告诉您,SPA 技术正在运行。该应用程序加载到浏览器中后,除非应用程序需要新数据,否则控制权绝不会转交给服务器。</p> <p>在 SPA 领域,浏览器就是新型的服务器。如果 10 个用户在运行该应用程序,就会有 10 个浏览器处理该负载。如果 1,000 个用户在运行该应用程序,则有 1,000 个浏览器处理该负载。整个 Angular 应用程序都在浏览器的控制之下,除了在服务器中运行的任何逻辑,比如身份验证和数据库操作。</p> <p>更高的性能和服务器压力的减轻,最终会提高用户体验和用户满意度 — 这是任何企业获得成功的关键。</p> <p>“ 在 SPA 领域,浏览器就是新型的服务器。 ”</p> <h2>结束语</h2> <p>您学习了如何使用 Angular 2 编写一个 SPA,并在开发计算机和沙箱服务器中运行它。对于生产需求,请与您的 IT 部门协商。大多数生产应用程序拥有的一项主要功能是身份验证和授权,应在服务器中运行该功能(主要出于安全原因)。您可能需要一个专用服务器来处理这些操作。对于该服务器,可以使用 Node.js,在 Angular 2 在前端运行时,它可以充当在后端运行的服务器。(Node 和 Angular 都起源于 Google,所以它们能有效地协同运行。)</p> <p>您可以考虑的用来提高应用程序性能的其他技术包括:</p> <ul> <li>捆绑:该进程将您的许多程序组合到一个文件中。</li> <li>微型化:压缩捆绑的文件,以便尽可能地减小项目大小。</li> <li>提前 (AoT) 编译:服务器复杂在构建过程中提前编译,而不是浏览器在运行时期间执行即时 (JIT) 编译。</li> </ul> <p> </p> <p> </p> <p>来自:http://www.ibm.com/developerworks/cn/web/wa-implement-a-single-page-application-with-angular2/index.html?ca=drs-</p> <p> </p>
本文由用户 qtuy9671 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!