使用 Visual Studio 2015 开发和部署 Angular 2 应用程序






4.95/5 (36投票s)
Angular 2 with TypeScript
引言
青少年时期,我热爱玩街机游戏《太空侵略者》,这款游戏于 1978 年创作并发布。它是现代电子游戏的先驱之一,并帮助将电子游戏产业从新奇事物扩展为一项全球性产业。《太空侵略者》在首次发布时取得了巨大成功。这可能是我第一次接触计算机技术。那时,我并不知道我最终会成为一名软件开发人员。
如果您是一名微软开发人员,并且最近一直关注微软开发领域,您可能会自言自语:“我们正在被入侵!”。随着 **MEAN 栈** 的普及及其对微软开发领域的影响,这些认识变得尤为明显。MEAN 栈是一套免费且开源的 JavaScript 软件栈,用于构建动态网站和 Web 应用程序。MEAN 栈使用了 MongoDB、Express.js、Angular 和 Node.js。由于 MEAN 栈的所有组件都支持用 JavaScript 编写的程序,因此 MEAN 应用程序可以使用一种语言同时在服务器端和客户端执行环境中使用。
微软开发人员现在正看到 MEAN 栈的成员 **Node.js** 进入他们的开发世界。Node.js 带来了事件驱动编程,使得开发 Web 服务器以及各种用 JavaScript 编写的工具库、框架和组件成为可能,从而加速和促进 Web 应用程序的开发。
为 Node.js 构建了数千个开源库,其中大部分托管在 **NPM** 网站上。
几年来,我最喜欢的 MEAN 栈技术之一是用于**单页应用程序 (SPA) 的 AngularJS**。AngularJS 是目前用于创建前端 Web 应用程序的最流行的 JavaScript 框架之一。
由于 Web 技术和浏览器的进步,AngularJS 的开发者 Google 几年前决定从头重写 AngularJS。与微软合作,AngularJS 被重新命名并使用 **TypeScript** 重写为 Angular 2。Angular 2 和 TypeScript 现在为 Web 应用程序的客户端带来了面向对象的 Web 开发,其语法与微软开发人员熟悉的 C# 非常相似。
我敢说,也许有一天,未来所有的 Web 应用程序将完全使用 JavaScript 和 TypeScript 等脚本技术从前端到后端进行开发。一切皆有可能。目前,可以肯定地说,微软开发人员可以继续在 Microsoft .NET 框架之上构建他们的 Web 应用程序。但是要小心,入侵已经开始——太空侵略者万岁。本文将稍微涉足 MEAN 栈领域,并介绍使用 Angular 2 作为前端应用程序,以及使用 **Visual Studio Professional 2015** 和一些 Node.js 工具进行应用程序开发和部署的过程。示例应用程序的后端将由 Microsoft .NET C# 业务层和数据访问层组成,并利用 Microsoft 的 .NET Web API 与 Microsoft Entity Framework 和 SQL-Server Express 集成。
在撰写本文时,Angular 2 仍处于开发中,但非常稳定。本文的示例应用程序是使用 Angular 2 的 Release Candidate 4 开发的。Release Candidate 5 已发布,因此 Angular 2 的最终发布应该会在年底前出来。
Visual Studio 2015 与 Visual Studio Code
2015 年,微软发布了一款名为 **Visual Studio Code** 的免费编辑器,这是一款轻量级的跨平台编辑器,用于编写可在 OS X、Linux 和 Windows 上运行的现代 Web 和云应用程序,并支持 IntelliSense、调试和 GIT。由于它轻量级且功能强大,Visual Studio Code 获得了极高的人气。您会看到很多关于 Angular 2 的文章和演示使用 Visual Studio Code。
Visual Studio 2015 仍然是大多数微软开发人员自 20 世纪 90 年代末推出 Visual InterDev 以来一直在使用的完整集成开发环境。Visual Studio Code 是微软的一个独立产品,与 Visual Studio 2015 完全不同。
如果您是一名微软 .NET 开发人员,并且是团队的一员,那么您的环境工作流程和软件开发管理流程很可能与微软的 Team Foundation Server (TFS) 紧密绑定,并且您正在使用 Visual Studio 2015 的某个版本(专业版或企业版)。Visual Studio 2015 与 TFS 和 MsBuild 工具及其 Agile/Scrum 模板集成。出于这些原因,我决定使用 **Visual Studio Professional 2015** 来撰写本文。
概述与目标
为了学习 Angular 2 (Release Candidate 4),本文的示例 Web 应用程序将包含以下功能和目标:
- 允许用户注册、登录和更新其用户配置文件
- 允许用户创建、更新和浏览客户数据库
- 创建一个具有 Angular 2 前端和 Microsoft .NET 后端的 Visual Studio 2015 项目
- 配置 Visual Studio 2015 将 Angular 2 TypeScript 文件编译为 JavaScript
- 配置构建、打包和最小化过程,用于开发和生产发布目的
- 提供缓存清除 Web 应用程序文件的机制
- 集成 Angular UI 的新版本控件和组件,用于 Angular 2
- 在需要时开发一些自定义的控件和组件
- 使用 Microsoft ASP.NET 启动 Web 应用程序
开发和部署 Angular 2 应用程序有很多内容需要讲解,因此我将本文分为两部分。第二部分名为《**使用 TypeScript 开发 Angular 2 应用程序**》,更深入地探讨了示例应用程序的源代码和 Angular 2 的一些关键方面。
本文的示例应用程序将使用 **Microsoft ASP.NET 4**。已经发布了一个新版本的 ASP.NET,名为 **ASP.NET Core**。ASP.NET Core 原名 ASP.NET 5,是对 ASP.NET 的重大重新设计,并进行了一系列架构更改,使其成为一个更精简、更模块化的框架。ASP.NET Core 不再基于 *System.Web.dll*。我认为这是微软对 Node.js 的普及及其轻量级占地面积和开放架构的回应。竞争是件好事。也许在未来的文章中,我会将 Angular 2 与 ASP.NET Core 集成,在此期间,还有很多东西需要学习。
安装和运行示例应用程序
下载并解压附带的源代码后,要运行示例应用程序,您需要在项目**根**文件夹的命令行中运行 **"npm install"**。这假定您已经在计算机上安装了 NodeJs。*node_modules* 文件夹很大,所以我只包含了编译应用程序所需的最低限度的 Node 模块。编译并启动应用程序后,您可以使用以下用户名和密码登录:
用户名:bgates@microsoft.com
密码:microsoft
可选地,您可以注册自己的登录名和密码。
创建空白 ASP.NET Web 应用程序项目
开发此应用程序的第一步是进入 Visual Studio 2015 并创建一个空的 ASP.NET Web 应用程序。本次演示中,Microsoft Web API 架构和管道已包含在项目中。项目解决方案将整合前端和后端代码,Web API 接受并响应 RESTful Web 请求,使用与 Visual Studio 2015 集成的 **IIS Express**。
在实际场景中,您可能希望为您的后端应用程序创建一个单独的项目。单独的后端项目将使编辑、编译和测试过程更加 streamlined。这种分离也将使您的后端代码在各种前端应用程序(如单独的桌面、Web 和移动前端)之间更具可重用性。
创建空白 ASP.NET 项目时,您会得到上述结构,其中包含一个普通的 *web.config* 文件。部署网站时,您通常希望已部署应用程序的 *Web.config* 文件中的某些设置与开发、QA 和生产等不同环境有所不同。例如,您可能希望管理连接字符串,使其指向每个环境的不同数据库。对于前端 Web 应用程序,您可能希望管理指向不同后端 Web API URL 的设置。**Web.config 转换** 与 Visual Studio 和 MSBuild 以及部署过程很好地集成。
通过 Node 包管理器安装 Angular 2
Angular 2 是通过 **Node 包管理器 (NPM)** 进行维护和安装的。Node.js 和 NPM 对 Angular 2 开发至关重要。这与微软 .NET 开发人员以前习惯的方式不同。微软 .NET 开发人员的主要包管理工具一直是 Visual Studio 自带的 **NuGet 包管理控制台**。幸运的是,安装 Visual Studio 2015 时也会安装 Node.js。在将 Angular 2 添加到项目之前,您需要在项目根文件夹中添加以下 *package.json* 文件。
// package.json
{
"name": "code-project",
"version": "1.0.0",
"author": "Mark Caplin",
"description": "Code Project Customer Maintenance Application",
"license": "ISC",
"dependencies": {
"@angular/common": "2.0.0-rc.4",
"@angular/compiler": "2.0.0-rc.4",
"@angular/core": "2.0.0-rc.4",
"@angular/forms": "0.2.0",
"@angular/http": "2.0.0-rc.4",
"@angular/platform-browser": "2.0.0-rc.4",
"@angular/platform-browser-dynamic": "2.0.0-rc.4",
"@angular/router": "^3.0.0-beta.2",
"bootstrap": "^3.3.6",
"es6-shim": "^0.35.0",
"ng2-dropdown": "0.0.4",
"reflect-metadata": "^0.1.3",
"rxjs": "5.0.0-beta.6",
"systemjs": "0.19.27",
"systemjs-builder": "^0.15.23",
"zone.js": "^0.6.12",
"ng2-bootstrap": "^1.0.23"
}
}
*package.json* 文件告诉 Node 在您从命令行运行 npm install
时安装什么。对于示例应用程序,Node 将安装 *package.json* 文件中引用的 Angular 2 的 Release Candidate 4。
执行“npm install
”命令后,您将得到一个名为 *node_modules* 的文件夹。所有 Angular 2 包和支持组件都将放入项目文件夹结构下的 *node_modules* 文件夹中。NPM 还管理您所需的包的所有依赖项。
首次导航到 *node_modules* 文件夹时,您会发现嵌套的 *node_modules* 文件夹。大多数 Microsoft Windows 工具、实用程序和 shell 无法处理超过 260 个字符的文件和文件夹路径。这个限制很容易被超过,一旦超过,安装脚本就会中断,并且无法使用 Microsoft Windows 环境中的常规方法删除 *node_modules* 文件夹。因此,您不希望将 *node_modules* 文件夹包含在项目中,也不希望直接从 *node_modules* 文件夹部署任何文件和子目录。
SystemJS 配置
SystemJS
是一个通用的动态模块加载器,也通过 NPM 安装。SystemJS
可以在浏览器中加载 ES6 模块、AMD 模块、CommonJS
和全局脚本。与您在线看到的大多数 Angular 2 演示一样,本应用程序将使用 SystemJS
来启动 Angular 2 应用程序。稍后在本文中,将使用 SystemJS
及其 Builder 工具来打包 Angular 2 和应用程序,以用于开发和生产部署目的。SystemJS
需要知道应用程序需要哪些包和组件,以及在哪里找到这些包。下面的示例 *system.config.js* 文件已添加到 Web 应用程序项目的**根**文件夹。
// systemjs.config.js
(function (global) {
// map tells the System loader where to look for things
var map = {
'application': 'application', // 'dist',
'rxjs': 'node_modules/rxjs',
'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api',
'@angular': 'node_modules/@angular',
'moment': 'node_modules/moment/moment.js',
'ng2-bootstrap/ng2-bootstrap': 'node_modules/ng2-bootstrap/ng2-bootstrap.js'
};
// packages tells the System loader how to load when no filename and/or no extension
var packages = {
'application': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
'angular2-in-memory-web-api': { defaultExtension: 'js' },
'moment': 'node_modules/moment/moment.js',
'ng2-bootstrap/ng2-bootstrap': { defaultExtension: 'js' },
};
var packageNames = [
'@angular/common',
'@angular/compiler',
'@angular/core',
'@angular/http',
'@angular/forms',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'@angular/router-deprecated',
'@angular/testing',
'@angular/upgrade',
];
//
// add package entries for angular packages in the form
// '@angular/common': { main: 'index.js', defaultExtension: 'js' }
//
packageNames.forEach(function (pkgName) {
packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
});
// filterSystemConfig - index.html's chance to modify config before we register it.
if (global.filterSystemConfig) { global.filterSystemConfig(config); }
System.config(config);
})(this);
TypeScript 类型定义
许多 JavaScript 库,如 jQuery、Jasmine 测试库以及 Angular 本身,都会用 TypeScript 编译器无法识别的功能和语法扩展 JavaScript 环境。当编译器无法识别某些内容时,它会抛出错误。
我们使用 **TypeScript 类型定义文件** — *d.ts* 文件 — 来告知编译器我们加载的库。一个名为 *typings.json* 的 TypeScript 类型配置文件的需要添加到项目根目录,内容如下:
// typings.json
{
"ambientDependencies": {
"jasmine": "registry:dt/jasmine#2.2.0+20160412134438",
"es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/
es6-shim.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd"
}
}
我们使用 NPM 安装了 typings 工具(它列在 *package.json* 的 devDependencies
中),并在 NPM 安装完成后添加了一个 NPM 脚本来自动运行该工具。此 typings 工具命令会将我们在 *typings.json* 文件中标识的 *d.ts* 文件安装到项目中的一个名为 *typings* 的文件夹中。
Index Razor 视图页面
您将在网上看到的绝大多数 Angular 2 演示都以一个 *index.html* 页面开始来启动应用程序。在本文的示例应用程序中,我决定在我的项目中添加一个名为 *index.cshtml* 的 **MVC Razor 视图页面**来启动应用程序。我发现 ASP.NET MVC 架构非常适合作为 AngularJS 和 Angular 2 应用程序的交付系统。
Razor 视图页面允许应用程序从 *web.config* 文件中提取服务器端设置。这使得将版本号、Web API 和基本 URL 等信息注入到启动过程中的索引页变得容易。Razor 视图页面还允许您编写 if
语句,以便为调试和生产模式生成索引页面。
<!-- index.cshtml -->
@{
Layout = null;
string version =
typeof(CodeProjectAngular2.Portal.Global).Assembly.GetName().Version.ToString();
Boolean includeBrowserSync = AppSettings["IncludeBrowserSync"]);
string currentRoute = "/";
// catch F5 reboot
foreach (string key in HttpContext.Current.Request.QueryString.AllKeys)
{
if (key == "CurrentRoute")
{
currentRoute = HttpContext.Current.Request.QueryString[key];
break;
}
}
}
<!DOCTYPE html>
<html>
<head>
<title>CodeProject Angular 2 </title>
<meta http-equiv=”Pragma” content=”no-cache”>
<meta http-equiv=”Expires” content=”-1″>
<meta http-equiv=”CACHE-CONTROL” content=”NO-CACHE”>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="@codeProjectBaseUrl" />
<script>
history.pushState({}, null, "@currentRoute");
</script>
<!-- Polyfill(s) for older browsers - 06/30/2016 -->
<!-- shim.min.js is from node_modules/corejs -->
<script src="~/scripts/shim.min.js?version=4"></script>
<script src="~/scripts/zone.min.js?version=4"></script>
<script src="~/scripts/Reflect.js?version=4"></script>
<script src="~/scripts/system.src.js?version=4"></script>
@if (HttpContext.Current.IsDebuggingEnabled)
{
<script src="ng2-bootstrap.min.js?version=4"></script>
<script src="~/Scripts/angular2-dev.min.js?version=4" ></script>
<!-- 2. Configure SystemJS -->
<script src="~/systemjs.config.js"></script>
<script>
System.config({
packages: { 'application': { defaultExtension: 'js' } }
});
System.import('application').catch(function (err) {
console.error(err);
});
</script>
}
<link href="~/content/bootstrap.css" rel="stylesheet" />
<link href="~/content/spinner.css" rel="stylesheet" />
<link href="~/content/style.css" rel="stylesheet" />
</head>
<body>
<codeproject-application currentRoute="@currentRoute">
<div>
...loading CodeProject Angular 2
</div>
</codeproject-application>
@if (HttpContext.Current.IsDebuggingEnabled && includeBrowserSync == true)
{
<!-- BrowserSync:SNIPPET-->
<script type='text/javascript' id="__bs_script__">
//<![CDATA[
document.write("<script async src='http://HOST:PORT
/browser-sync/browser-sync-client.js'><\/script>"
.replace("HOST", location.hostname)
.replace("PORT", parseInt(location.port) + 1));
//]]>
</script>
<!-- BS:BrowserSyncs:END-->
}
@if (HttpContext.Current.IsDebuggingEnabled==false)
{
<script src="~/scripts/angular2.min.js?version=@version" ></script>
}
</body>
</html>
TypeScript 和应用程序目录
您首先需要考虑的是为您的 Angular 2 应用程序创建一个应用程序文件夹,以及该目录结构的外观。应用程序文件夹将主要包含您的应用程序 HTML 模板和 TypeScript 组件。为了快速启动,初始文件夹结构可能如下所示,包括传统的关于、联系、主页和主页。
自动版本号和缓存清除
对于这个示例应用程序,我希望每次使用 *AssemblyInfo.cs* 文件(位于 *Properties* 文件夹下)中的信息编译、测试和发布应用程序时,都能跟踪版本和构建号。每次应用程序运行时,我都希望获取应用程序的最新版本,并使用版本号来帮助执行诸如在 JavaScript 文件末尾附加版本号之类的操作,这将告诉浏览器获取这些文件的新版本,而不是从浏览器缓存中运行旧版本的文件。为了方便起见,我从 此处下载了一个 **Visual Studio Professional 2015 的自动版本插件**。
该插件会自动为 C# 和 VB.NET 项目递增程序集版本。下载会将插件安装到工具菜单中,称为 **Automatic Version Settings**。该插件附带一个配置工具,允许您配置主要和次要构建号,这些构建号将在每次编译时自动更新您的 *AssemblyInfo.cs* 文件。另外,您可以手动更新版本号,或使用 Microsoft 的 TFS 等工具来管理构建号,在一个完全集成的持续构建和配置管理环境中。
旧浏览器的 Polyfill
我们需要添加到 Angular 2 项目中的内容之一是 polyfill 脚本。在 Web 开发中,**polyfill** 是在不支持该功能的 Web 浏览器上实现功能的代码。最常见的是,它指的是一个 JavaScript 库,它在一个 HTML5 Web 标准上实现了一个功能,该功能要么是在旧浏览器中支持的已建立标准(被一些浏览器支持),要么是在现有浏览器中支持的提议标准(未被任何浏览器支持)。
正式来说,polyfill 是浏览器 API 的一个 shim。Polyfill 允许 Web 开发人员无论浏览器是否支持 API,都能使用该 API,而且通常开销很小。通常,它们首先检查浏览器是否支持 API,如果可用则使用它,否则使用自己的实现。Polyfill 本身使用其他更受支持的功能,因此可能需要不同的 polyfill 来应对不同的浏览器。
对于 Angular 2 应用程序,需要一些 polyfill。对于示例应用程序,我创建了一个 *Scripts* 文件夹,并将所需的 JavaScript polyfill 文件从 *node_modules* 文件夹复制并添加到此文件夹中——请注意,在生产环境中,我不想直接包含 *node_modules* 文件夹中的任何内容。索引页以如下方式引用这些文件中的 script
标签:
<!-- index.cshtml -->
<!-- Polyfills for older browers -->
<!-- from node_modules\core-js\client--> <script src="~/Scripts/core.min.js"></script>
<!-- from node_modules\zone.js\dist--> <script src="~/Scripts/zone.min.js"></script>
<!-- from node_modules\reflect-metadata--> <script src="~/Scripts/Reflect.js"></script>
<!-- from node_modules\systemjs\dist--> <script src="~/Scripts/system.src.js"></script>
每个 Angular 2 应用程序都需要一个主引导组件、一个根应用程序组件、一个主页和一个路由组件。以下是这些组件的一些样板代码:
/* main.ts */
///<reference path="../typings/browser.d.ts"/>
import { bootstrap } from '@angular/platform-browser-dynamic';
import { disableDeprecatedForms, provideForms } from "@angular/forms";
import { ApplicationComponent } from './application.component';
import { applicationRouterProviders } from "./application.routes";
import { enableProdMode} from '@angular/core';
enableProdMode();
bootstrap(AppComponent, [
applicationRouterProviders,
disableDeprecatedForms(),
provideForms()
]);
/* application.component.ts */
import { Component } from '@angular/core';
import { HTTP_PROVIDERS, HTTP_BINDINGS } from '@angular/http';
import { MasterComponent } from './master.component';
import 'rxjs/Rx';
@Component({
selector: 'code-project-application',
template: '<master></master>',
directives: [MasterComponent],
providers: [HTTP_BINDINGS,HTTP_PROVIDERS]
})
export class AppComponent {
constructor() {}
}
/* applications.routes.ts */
import { provideRouter, RouterConfig } from "@angular/router";
import { AboutComponent } from './home/about.component';
import { ContactComponent } from './home/contact.component';
import { HomeComponent } from './home/home.component';
const routes: RouterConfig = [
{ path: 'home/about', component: AboutComponent },
{ path: 'home/contact', component: ContactComponent },
{ path: 'home/home', component: HomeComponent }
];
export const applicationRouterProviders = [
provideRouter(routes)
];
/* master.component.ts */
import { Component, OnInit } from '@angular/core';
import { HTTP_PROVIDERS } from '@angular/http';
import { ROUTER_DIRECTIVES } from '@angular/router';
@Component({
selector: 'master',
templateUrl: 'application/master.component.html',
directives: [ROUTER_DIRECTIVES]
})
export class MasterComponent implements OnInit {
constructor() {}
public ngOnInit() {
}
}
TypeScript 和 Visual Studio 2015
如果您是 C# 开发人员,您会喜欢使用 TypeScript 来开发 Angular 2 应用程序。它具有客户端的 C# 的外观和感觉,包括对强类型对象和属性的支持。TypeScript 从 JavaScript 的语法和语义开始。TypeScript 编译成干净、简单的 JavaScript 代码,可以在任何浏览器、Node.js 或任何支持 ECMAScript 3(或更高版本)的 JavaScript 引擎中运行。
TypeScript 与 Visual Studio 2015 集成,并提供完整的类和组件 IntelliSense。当您保存 TypeScript 文件并进行配置时,Visual Studio 2015 会自动将您的 TypeScript 文件编译为 JavaScript 文件。
此外,当您从 Visual Studio 构建应用程序时,Visual Studio 2015 会重新编译所有 TypeScript 文件。如果存在任何 TypeScript 语法错误,构建将失败。这很棒。现在,作为开发人员,您可以放心,您的 JavaScript 在编译时已经过语法错误和类型错误的检查,而不是在生产环境中发现错误。
首次在 Visual Studio 中编译应用程序时,TypeScript 会出现错误。您可能会看到类似以下的错误:**错误 TS1219 装饰器的实验性支持** 是一个可能在未来版本中更改的功能。设置 'experimentalDecorators
' 选项以消除此警告。
要解决此问题,您需要编辑项目文件并添加一些 TypeScript 选项。您可以从 *Windows* 文件夹编辑文件,或者在 Visual Studio 中卸载项目(右键单击项目名称,选择 **卸载项目**,然后选择编辑 *projectname.csproj* 文件,进行更改,然后重新加载项目);
需要以下三个选项在您的项目文件中:
<!-- CodeProjectAngular2.csproj -->
<TypeTypeScriptModuleResolution>NodeJs</TypeScriptModuleResolution>
<TypeScriptExperimentalDecorators>true</TypeScriptExperimentalDecorators>
<TypeScriptEmitDecoratorMetadata>true</TypeScriptEmitDecoratorMetadata>
您的项目文件应类似于以下内容,其中包括添加三个附加设置,并为每个可能的配置(dev、qa、production、release)制作一份副本。TypeScript 编译器将在编译期间读取这些设置。
<!-- CodeProjectAngular2.csproj -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)'== Debug|AnyCPU'">
<TypeScriptTarget>ES5</TypeScriptTarget>
<TypeScriptJSXEmit>None</TypeScriptJSXEmit>
<TypeScriptCompileOnSaveEnabled>True</TypeScriptCompileOnSaveEnabled>
<TypeScriptNoImplicitAny>False</TypeScriptNoImplicitAny>
<TypeScriptModuleKind>CommonJS</TypeScriptModuleKind>
<TypeScriptRemoveComments>False</TypeScriptRemoveComments>
<TypeScriptOutFile />
<TypeScriptOutDir />
<TypeScriptGeneratesDeclarations>False</TypeScriptGeneratesDeclarations>
<TypeScriptNoEmitOnError>True</TypeScriptNoEmitOnError>
<TypeScriptSourceMap>True</TypeScriptSourceMap>
<TypeScriptModuleResolution>NodeJs</TypeScriptModuleResolution>
<TypeScriptExperimentalDecorators>true</TypeScriptExperimentalDecorators>
<TypeScriptEmitDecoratorMetadata>true</TypeScriptEmitDecoratorMetadata>
<TypeScriptMapRoot />
<TypeScriptSourceRoot />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<TypeScriptTarget>ES5</TypeScriptTarget>
<TypeScriptJSXEmit>None</TypeScriptJSXEmit>
<TypeScriptCompileOnSaveEnabled>True</TypeScriptCompileOnSaveEnabled>
<TypeScriptNoImplicitAny>False</TypeScriptNoImplicitAny>
<TypeScriptModuleKind>CommonJS</TypeScriptModuleKind>
<TypeScriptRemoveComments>False</TypeScriptRemoveComments>
<TypeScriptOutFile />
<TypeScriptOutDir />
<TypeScriptGeneratesDeclarations>False</TypeScriptGeneratesDeclarations>
<TypeScriptNoEmitOnError>True</TypeScriptNoEmitOnError>
<TypeScriptSourceMap>True</TypeScriptSourceMap>
<TypeScriptModuleResolution>NodeJs</TypeScriptModuleResolution>
<TypeScriptExperimentalDecorators>true</TypeScriptExperimentalDecorators>
<TypeScriptEmitDecoratorMetadata>true</TypeScriptEmitDecoratorMetadata>
<TypeScriptMapRoot />
<TypeScriptSourceRoot />
</PropertyGroup>
添加这些新设置后,您的 TypeScript 文件将在保存时自动编译。不幸的是,当您构建整个应用程序时,您将遇到以下 TypeScript 编译器错误:
error TS6063:Build:Argument for '--moduleResolution' option must be 'node' or 'classic'.
在项目设置中,我们需要将 TypeScript 编译器 moduleResolution
参数设置为“NodeJs
”才能使**即时编译**生效。问题是,当 MsBuild 运行 TypeScript 编译器时,需要将 moduleResolution
设置为 'node
'。这似乎是 Visual Studio 2015 设置中的一个 bug。要覆盖并修复此问题 — 直到补丁发布 — 您还需要在项目文件的底部添加以下内容,该内容将在构建过程中执行,并将覆盖 TypeScript 编译器 moduleResolution
选项到它成功运行所需的设置。
<!-- CodeProjectAngular2.csproj -->
<PropertyGroup>
<CompileTypeScriptDependsOn>
SpoofTypeScriptModuleResolution;$(CompileTypeScriptDependsOn)
</CompileTypeScriptDependsOn>
</PropertyGroup>
<Target Name="SpoofTypeScriptModuleResolution">
<PropertyGroup>
<TypeScriptBuildConfigurations>$(
TypeScriptBuildConfigurations.Replace
('--moduleResolution NodeJs', '--moduleResolution node'))
</TypeScriptBuildConfigurations>
</PropertyGroup>
<Message Text="Options: $(TypeScriptBuildConfigurations)" />
</Target>
Bootstrap 和 Angular 2 UI
现在我们已准备好运行应用程序。但在那之前,我们应该为应用程序添加一些 Bootstrap 样式。在 AngularJS 中,我们有 Angular UI,它提供了一个指令和组件工具箱,增强了 AngularJS 的前端,包括日期选择器、警告框和各种其他组件。
对于 Angular 2,我发现了 **ng2-bootstrap**(由 Valor Software 支持),它提供了 Angular 2 的等效指令集。标准的 bootstrap css 文件和 ng2-bootstrap 都是通过初始 npm install 安装的,并存在于 *node_modules* 文件夹中。以下两个标签已添加到索引页:
<!-- index.cshtml -->
<script src="~/scripts/ng2-bootstrap.min.js?version=4"></script>
<link href="~/application/content/bootstrap.css?version=4" rel="stylesheet" />
<!--node_modules\bootstrap\dist\css-->
bootstrap css 文件已复制到项目的 content 文件夹。在开发模式下,索引页将引用 ng2-bootstrap 指令,这些指令被打包在一个文件中。
运行应用程序
本文的示例应用程序通过运行索引 razor 页面启动,该页面引用 SystemJS
。SystemJS
找到主 Angular 2 引导文件 *main.js* 并引导 Angular 2 来启动应用程序。
<!-- index.cshtml -->
<script src="~/systemjs.config.js"></script>
<script>
System.config({
packages: { 'application': { defaultExtension: 'js' } }
});
System.import('application').catch(function (err) {
console.error(err); }
);
</script>
首次运行应用程序时,尤其是在 Internet Explorer 中,您会发现应用程序加载需要一些时间。您可能会问:“怎么回事,我以为 Angular 2 应该比 AngularJS 快?”。深入研究这个问题,我决定打开 Internet Explorer 开发工具中的**网络选项卡**,并重新加载应用程序以查看发生了什么。
**网络**选项卡揭示了一个令人震惊的发现。应用程序的首次运行向 IIS Express 发送了 446 个请求。结果是,当您在索引页中使用 SystemJS
启动应用程序时,它会遍历整个应用程序并从 Angular 2 node_modules 目录加载所有引用和依赖项,包括您自己的应用程序代码。
尽管大部分内容已被缓存,但 IIS Express 的命中次数导致应用程序加载延迟长达 30 秒。需要采取措施解决这个问题。第一个问题是,如何减少网络请求?显然,这在生产环境中是不可接受的。
使用 Gulp 为开发打包 Angular 2
在寻找简化开发流程、加快应用程序初始加载速度并减少 IIS Express 请求数量的方法后,我意识到我并不需要每次应用程序启动时都重新加载所有 Angular 2 组件。我可以将所有 Angular 2 组件打包到一个文件中,从而只需一次请求即可将 Angular 2 加载到浏览器。
**Gulp** 是一个工具包,可帮助您自动化开发工作流程中痛苦或耗时的任务。Gulp 集成到所有主要的 IDE 和平台,包括 PHP、.NET、Node.js、Java 和其他平台。Gulp 使用 NPM 模块通过 2000 多个用于流式文件转换的插件来执行任何您想要的操作。
通过 *package.json* 文件中的引用,Gulp 和我想要的插件在从命令行运行 npm install 时被安装。要运行 Gulp 任务,您需要创建一个 *gulpfile.js* 文件并将其添加到项目根目录。
下面的 gulpfile 运行两个任务,并使用 **SystemJS Builder** 的 bundle 方法将所有单独的 Angular 2 组件编译成 **CommonJS** 格式,保存在项目 *scripts* 文件夹下的一个临时目录中。编译 Angular 2 组件后,将执行一个额外的 Gulp 任务,该任务将所有代码通过管道传输到一个连接和最小化的 JavaScript 文件中。
Gulp 中使事情变得简单的关键功能之一是其**管道和流式传输**功能。Gulp 使用管道来流式传输需要处理的数据。一个函数的输入可以被处理并作为输出传递给另一个函数。本质上,您可以在单个命令中执行一系列步骤,并且所有这些都在内存中完成,然后再将输出写入磁盘。
// gulpfile.js
var gulp = require('gulp');
var Builder = require('systemjs-builder');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var runSequence = require('run-sequence');
var appProd = './scripts';
var appDevTemp = './scripts/temp/angular2';
//
// the following series of tasks builds each component of angular 2 into a temp directory
// for caching in development mode
//
gulp.task('@angular.js', function ()
var SystemBuilder = require('systemjs-builder');
var builder = new SystemBuilder('./', 'systemjs.config.js');
builder.bundle('@angular/core', appDevTemp + '/core.js');
builder.bundle('@angular/compiler', appDevTemp + '/compiler.js');
builder.bundle('@angular/forms', appDevTemp + '/forms.js');
builder.bundle('@angular/common', appDevTemp + '/common.js');
builder.bundle('@angular/http', appDevTemp + '/http.js');
builder.bundle('@angular/router', appDevTemp + '/router.js');
builder.bundle('@angular/platform-browser', appDevTemp + '/platform-browser.js');
builder.bundle('@angular/platform-browser-dynamic',
appDevTemp + '/platform-browser-dynamic.js');
builder.bundle('rxjs/Rx', appDevTemp + '/rxjs.js');
return;
});
//
// minify the development build for angular 2
//
gulp.task('buildForDevelopment', ["@angular.js"],
function () {
return gulp.src( appDevTemp +'/*.js').pipe(uglify())
.pipe(concat('angular2-dev.min.js')).pipe(gulp.dest(appProd));
});
上述 Gulp 任务的最终结果是单个最小化的 Angular 2 JavaScript 文件,该文件可以通过 script
标签引用。下面的代码片段已添加到索引 razor 视图页面,当在调试模式下运行时,它会条件性地添加对 *angular2-dev.min.js* 文件的 script
标签引用。当安装新版本的 Angular 2 时,您只需重新运行 bundle 任务即可。
<!-- index.cshtml -->
@if (HttpContext.Current.IsDebuggingEnabled==true)
{
<script src="~/Scripts/angular2-dev.min.js?version=4" ></script>
}
您可以通过 Visual Studio 2015 的 Task Runner Explorer 窗口执行 Gulp 任务,如下所示。您还可以通过任务运行器窗口将任务绑定到特定事件(如构建事件),告诉 Visual Studio 2015 自动执行任务。此外,您还可以简单地通过从命令行键入“gulp taskname
”来运行 Gulp 任务。
创建了 Angular 2 组件的这个打包文件后,我再次启动了示例应用程序,IIS Express 的请求数量大大减少,应用程序的启动速度也快了很多,从大约 30 秒缩短到不到 10 秒。
SystemJS
builder 工具是一个很棒的工具,它为将所有模块格式编译成单个包提供了全面的支持。
BrowserSync Gulp Watcher
您可以使用 Gulp 实现许多插件和工具来增强您的开发体验。我的另一个最爱是 **Gulp watcher** 函数。
Gulp watcher 函数内置于 Gulp 中。您可以告诉 Gulp 监视项目中的某些文件更改,并自动刷新您的 Web 浏览器以显示更改,而无需您手动按 F5 键。有各种浏览器插件可以帮助实现此功能,例如 **Livereload** 和 **BrowserSync**。
本文的示例应用程序使用 BrowserSync
。要开始,您需要设置一个 Gulp 任务来运行,该任务会查找项目中的文件系统更改,如下所示:
// gulpfile.js
var browserSync = require('browser-sync').create();
var iisPort = 55059;
//
// configure browser sync
//
gulp.task('sync', function () {
browserSync.init({
port: iisPort + 1
});
gulp.watch("application/**/*.html").on('change', browserSync.reload);
gulp.watch("application/**/*.js").on('change', browserSync.reload);
gulp.watch("*.cshtml").on('change', browserSync.reload);
});
设置 Gulp browser sync 任务需要设置一个 IIS 端口,并告知 Gulp 要监视哪些文件模式的变化。在上面的示例中;该任务引用了示例应用程序正在使用的 IIS Express 的 IIS 端口,并将该端口号加一,以便在一个单独的端口上监听更改。Gulp watcher 任务设置为监视应用程序目录树下的 JavaScript 和 HTML 文件。索引 razor 页面的更改也在被监视。
<!-- index.cshtml -->
@{
Boolean includeBrowserSync = ConfigurationManager.AppSettings["IncludeBrowserSync"];
}
@if (HttpContext.Current.IsDebuggingEnabled == true && includeBrowserSync == true)
{
<!-- BrowserSync:SNIPPET-->
<script type='text/javascript' id="__bs_script__">
//<![CDATA[
document.write
("<script async src='http://HOST:PORT/browser-sync/browser-sync-client.js'<\/script>"
replace("HOST", location.hostname).replace("PORT", parseInt(location.port) + 1));
//]]>
</script>
<!-- BS:BrowserSyncs:END-->
}
下一步是在浏览器中设置一个监听器,通过在索引 razor 视图页面中注入一个 Script
标签来启用 browser sync 监听。示例应用程序将引用 *web.config* 文件中的一个标志,该标志可以打开和关闭。在调试模式下,并且当 includeBrowserSync
标志设置为 true
时,script
标签将实现 browser sync 监听器。
现在要让一切运行起来,您只需要在命令行中键入“gulp gulptaskname
”,监视器就会无限期运行,直到您从命令行关闭 gulp
进程。
为了在开发过程中帮助清除浏览器缓存,我在组件中所有 HTML 模板的末尾添加了一个日期时间戳。这样,我就能确保浏览器会获取 HTML 模板的最新更改。稍后在生产构建期间,此时间戳将从组件中删除。
export var debugVersion = "?version=" + Date.now();
@Component({
templateUrl: 'application/home/about.component.html' + debugVersion
})
通过 MVC 在浏览器刷新时维护当前路由
使用 MVC 作为应用程序引导器的副作用之一是,每次执行浏览器刷新(无论是通过自动浏览器同步工具还是手动刷新浏览器)时,都会启动一个新的服务器请求,该请求会返回 IIS。为了维护当前页面的路由,我必须 hack MVC 路由管道。
我希望将所有 Web 请求重定向回索引 razor 视图页面,而无需为 MVC 控制器、操作和视图创建文件夹。基本上,我想要一个无控制器的 MVC Web 应用程序,并获取当前路由并将其传递给索引 razor 视图页面,以便保留当前路由。
为了实现这一点,我找到了 Brent Jenkin 在 CodeProject 上的**《不带控制器或操作的 ASP.NET MVC 用法》**文章。
这正是我想要的。将 Brent 文章中的代码添加到 *AppStart* 目录后,我简单地修改了 DispatchRequest
方法,并将请求重定向回默认的索引 razor 视图页面,并在查询字符串中追加了当前路由。
private void DispatchRequest
(IControllerFactory controllerFactory, string controller, string action)
{
string currentRoute = _requestContext.HttpContext.Request.CurrentExecutionFilePath;
string defaultPage = AppSettings["DefaultPage"].ToString();
_requestContext.HttpContext.Response.Redirect(defaultPage +
"?referral" + currentRoute + "&" + "CurrentRoute=" + currentRoute);
}
Controllerless
路由管道在 MVC 注册其路由配置时的 RouteConfig
类中配置。
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
var route = new Route(
"{controller}/{action}/{id}", new RouteValueDictionary(new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
}),
new CodeProjectAngular2.Portal.ControllerLess.Mvc.ControllerLessRouteHandler());
routes.Add(route);
}
}
索引 razor 视图页面首先检查查询字符串中的当前路由,如果提供了,则通过页面标题中的 pushState
方法将路由注入到浏览器的历史记录中。最后,通过 Angular 2 输入参数将当前路由注入到 Angular 2 应用程序中。Angular 2 应用程序将简单地导航到当前路由(如果提供了该值)。
// index.cshtml
@{
string currentRoute = "/";
foreach (string key in HttpContext.Current.Request.QueryString.AllKeys)
{
if (key == "CurrentRoute")
{
currentRoute = HttpContext.Current.Request.QueryString[key];
}
}
}
<script>
history.pushState({}, null, "@currentRoute");
</script>
<codeproject-application title="@title"
currentRoute="@currentRoute" version="@version">
<div>
...loading
</div>
</codeproject-application>
// master.component.ts code snippet
if (this.currentRoute == "/") {
this.router.navigate(['/home/home']);
return;
}
else {
this.router.navigate([this.currentRoute]);
}
}
为生产环境打包整个示例应用程序
开发完示例应用程序后,我准备将其部署到生产 IIS Web 服务器。打包生产应用程序的最新趋势之一是将整个应用程序打包到一个 JavaScript 文件中,包括相关的 HTML 模板和 CSS 文件。我最大的问题是如何打包应用程序的所有组件,包括 Angular 2,到一个单一的包中。
将整个应用程序打包到一个 JavaScript 文件中的一个优点是,只需一次向 Web 服务器发出请求,即可将整个应用程序下载到客户端并进行缓存。后续的应用程序资源请求将来自客户端,从而大大减少 Web 服务器上的网络流量。
在下面的关于页面的 Angular 2 组件中,您会注意到 HTML 模板是通过 templateUrl
属性引用的。这对于开发非常有用,因为它允许您将 HTML 与 TypeScript 代码分开。
// about.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
templateUrl: 'application/home/about.component.html'
})
export class AboutComponent implements OnInit {
public title: string;
constructor() { }
public ngOnInit() {
this.title = "About";
}
}
对于生产目的,我希望替换 templateUrl
标签,并通过 @Component
注释的 template
属性将 HTML 模板直接注入到 TypeScript 组件中。这将有助于将整个应用程序打包到一个 JavaScript 文件中。
在生产环境中运行应用程序时,我不想依赖任何模块加载器,如 SystemJS
。我只想在索引 razor 页面中插入一个 script
标签,引用整个应用程序的生产 JavaScript 文件。
对于生产构建,我重新审视了 SystemJS
builder 工具,它提供了创建**自执行包**的能力,这些包无需 SystemJS
即可运行,只需将一个**微加载器**实现嵌入到打包的 JavaScript 文件中。
打包过程的第一步是将 HTML 模板注入到其关联的 JavaScript 文件中。Visual Studio 的好处之一是,它会在构建应用程序时将所有应用程序 TypeScript 文件重新编译为 JavaScript。
有几个 Gulp 插件可以执行 HTML 注入,如 gulp-inline-ng2-template
。作为一项实验,我想编写自己的自定义 Gulp 任务来执行 HTML 注入。编写 Gulp 任务的好处是,您可以编写纯 JavaScript 代码并创建各种自定义任务。当然,我建议阅读 Gulp 文档。很可能已经有一个 Gulp 插件可以执行您需要的任务。
// gulpfile.js
gulp.task("buildForProduction", function () {
console.log('injectHTML started');
var i = 0;
return gulp.src('application/**/*.js').pipe(foreach(function (stream, file) {
var name = file.path;
var fileStream = fs.readFileSync(name, "utf8");
var fileContents = fileStream.split("\n");
var rows = fileContents.length;
var output = "";
for (var i = 0; i < rows; i++) {
var currentLine = fileContents[i];
var outputLine = currentLine;
if (currentLine.indexOf('application/') > -1) {
if (currentLine.indexOf('templateUrl') > -1) {
currentLine = currentLine.replace("+ exports.debugVersion", "");
var start = currentLine.indexOf("application");
var end = currentLine.indexOf(".html");
var lengthOfName = end - start;
var htmlFileName = "./" + currentLine.substr(start, lengthOfName) + ".html";
var comma = currentLine.indexOf(",");
try {
var htmlContent = fs.readFileSync(htmlFileName, "ASCII");
htmlContent = htmlContent.replace('/[\x00-\x1F\x80-\xFF]/', '');
htmlContent = htmlContent.replace("o;?", "");
htmlContent = htmlContent.replace(/"/g, '\\"');
htmlContent = htmlContent.replace(/\r\n/g, '');
htmlContent = "\"" + htmlContent + "\"";
currentLine = "template: " + htmlContent;
if (comma > -1) currentLine = currentLine + ",";
outputLine = currentLine;
}
catch (err) {
console.log(htmlFileName + " not found.")
}
}
}
output = output + outputLine;
}
streams = [];
var stream = source("inject.js");
var streamEnd = stream;
stream.write(output);
process.nextTick(function () {
stream.end();
});
streamEnd = streamEnd.pipe(vinylBuffer()).pipe
(concat(name)).pipe(gulp.dest("./application"));
return stream;
})).on('end', function () {
var builder = new Builder('./', 'systemjs.config.js');
builder.buildStatic('./application/main.js',
'scripts/angular2.min.js', { minify: true, sourceMaps: false }
).then(function () {
console.log('Build static complete');
});
});
});
在上面的 *Gulpfile.js* 示例中,执行了一个 Gulp 任务,该任务首先**注入**与每个 JavaScript 组件关联的 HTML 模板。该任务使用 gulp.src
方法运行,该方法导航整个应用程序目录结构,查找 JavaScript 文件并单独处理每个文件。
每个文件的源代码被**管道传输**到一个 foreach
循环中,任务在该循环中查找 templateUrl
代码行,提取 HTML 模板的文件名,读取 HTML 文件,并将 templateURL
行替换为**内联 HTML**。templateUrl
属性被 template
属性替换。
一旦注入部分任务完成,“buildForProduction
”任务继续执行,并使用 SystemJS
Builder 工具。创建开发包时,使用了构建器工具的“bundle
”方法。对于生产环境,我想要创建一个自执行包,可以在生产环境中无需 SystemJS
即可运行。使用 SystemJS
Builder 的“buildStatic
”方法将会在 JavaScript 包末尾嵌入一个微加载器。
要配置 SystemJS
Builder,您需要提供对 *systemjs.config.js* 文件的引用,以便它知道如何定位所有应用程序组件。buildStatic
方法有两个参数。第一个参数是应用程序主组件的名称,在本例中是 *main.js*。主组件是应用程序的顶层根组件。从那里,buildStatic
方法会遍历整个应用程序查找所有组件及其依赖项,并按正确的顺序组合所有组件及其依赖项;这是模块加载器的主要优点之一。
第二个参数告诉 SystemJS
builder 工具代码将被打包到的文件的目标和名称。最后,SystemJS
builder 工具有一个**minify 选项**,设置为 true
时,它会为您创建一个最小化的 JavaScript 文件。
这一切都很奏效,直到构建器工具遇到对 **ng2-bootstrap** 组件的引用。出于某种原因,在 ng2-bootstrap
组件的 *node_modules* 目录中,构建找不到所有组件和依赖项的路径。这迫使我编辑 *node_modules* 目录中的 ng2-bootstrap
组件,并提供更强的路径信息,包括在所有 require
语句中添加“.js”扩展名。
例如,我不得不更改下面的 *ng2-bootstrap.js*,在 require
语句中使用完整的路径和 JavaScript 扩展名。也许以后,我会找出真正的问题所在,并在未来修复它。
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
var accordion_1 = require('./components/accordion/accordion.component.js');
var alert_1 = require('./components/alert/alert.component.js');
var buttons_1 = require('./components/buttons/button-checkbox.js');
var buttons_2 = require('./components/buttons/button-radio.js');
var carousel_1 = require('./components/carousel/carousel.component.js');
var collapse_1 = require('./components/collapse/collapse.directive.js');
var datepicker_1 = require('./components/datepicker/date-formatter.js');
var datepicker_2 = require('./components/datepicker/inner.component.js');
var datepicker_3 = require('./components/datepicker/popup.component.js');
var datepicker_4 = require('./components/datepicker/date.component.js');
var datepicker_5 = require('./components/datepicker/month.component.js');
var datepicker_6 = require('./components/datepicker/year.component.js');
var dropdown_1 = require('./components/dropdown/dropdown-menu.js');
var dropdown_2 = require('./components/dropdown/dropdown-toggle.js');
var dropdown_3 = require('./components/dropdown/dropdown.directive.js');
var dropdown_4 = require('./components/dropdown/dropdown.service.js');
var modal_1 = require('./components/modal/modal.component.js');
var pagination_1 = require('./components/pagination/pagination.js');
var progressbar_1 = require('./components/progressbar/progressbar.js');
var rating_1 = require('./components/rating/rating.component.js');
var tabs_1 = require('./components/tabs/tab-heading.directive.js');
var tabs_2 = require('./components/tabs/tab.directive.js');
var tabs_3 = require('./components/tabs/tabset.component.js');
var timepicker_1 = require('./components/timepicker/time.component.js');
var tooltip_1 = require('./components/tooltip/tooltip-container.js');
var tooltip_2 = require('./components/tooltip/tooltip-options.class.js');
var tooltip_3 = require('./components/tooltip/tooltip.directive.js');
var typeahead_1 = require('./components/typeahead/typeahead.component.js');
var typeahead_2 = require('./components/typeahead/typeahead-options.js');
var typeahead_3 = require('./components/typeahead/typeahead-utils.js');
var typeahead_4 = require('./components/typeahead/typeahead.directive.js');
var components_helper_service_1 =
require('./components/utils/components-helper.service.js');
__export(require('./components/accordion.js'));
__export(require('./components/alert.js'));
__export(require('./components/buttons.js'));
__export(require('./components/carousel.js'));
__export(require('./components/collapse.js'));
__export(require('./components/datepicker.js'));
__export(require('./components/modal.js'));
__export(require('./components/dropdown.js'));
__export(require('./components/pagination.js'));
__export(require('./components/progressbar.js'));
__export(require('./components/rating.js'));
__export(require('./components/tabs.js'));
__export(require('./components/timepicker.js'));
__export(require('./components/tooltip.js'));
__export(require('./components/typeahead.js'));
__export(require('./components/position.js'));
__export(require('./components/common.js'));
__export(require('./components/ng2-bootstrap-config.js'));
exports.BS_VIEW_PROVIDERS = [{
provide: components_helper_service_1.ComponentsHelper,
useClass: components_helper_service_1.ComponentsHelper }];
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
directives: [
alert_1.AlertComponent,
accordion_1.ACCORDION_DIRECTIVES,
buttons_1.BUTTON_DIRECTIVES,
carousel_1.CAROUSEL_DIRECTIVES,
collapse_1.CollapseDirective,
datepicker_1.DATEPICKER_DIRECTIVES,
dropdown_1.DROPDOWN_DIRECTIVES,
modal_1.MODAL_DIRECTIVES,
pagination_1.PAGINATION_DIRECTIVES,
progressbar_1.PROGRESSBAR_DIRECTIVES,
rating_1.RatingComponent,
tabs_1.TAB_DIRECTIVES,
timepicker_1.TimepickerComponent,
tooltip_1.TOOLTIP_DIRECTIVES,
typeahead_1.TYPEAHEAD_DIRECTIVES
],
providers: [
components_helper_service_1.ComponentsHelper
]
};
一旦我将整个应用程序打包到一个 JavaScript 文件中,我只需要在索引 razor 页面的 body 末尾添加一个 script
标签来引用它。在下面的代码片段中,当在生产模式下运行时,会加载 *angular2.min.js* 文件。应用程序版本号会被追加到 JavaScript 文件中,以便客户端的浏览器缓存被刷新(**缓存清除**)为新版本的文件。
要模拟生产模式进行测试,您可以编辑 *web.config* 文件并将 debug 设置更改为 false
。
@if (HttpContext.Current.IsDebuggingEnabled==false) {
<script src="~/scripts/angular2.min.js?version=@version" ></script>
}
能够将整个 Web 应用程序的前端打包成一个文件并将其下载到客户端,这真是令人惊叹。这几乎就像从移动应用程序商店下载和安装一个移动应用程序。一旦下载完成,唯一需要与您的服务器进行的请求就是 RESTful 服务调用。
从 Visual Studio 2015 发布
Visual Studio 2015 的优点之一是您可以通过**发布菜单**选项发布您的应用程序。我为这个示例应用程序设定的目标之一是能够将其从 Visual Studio 发布到 QA 或生产站点。
为了实现这一点,需要 Gulp buildForProduction
任务在 Visual Studio 的构建/发布过程中运行。我不希望每次发布应用程序时都手动运行所需的 Gulp 任务。幸运的是,我们可以在 Visual Studio 2015 中自动运行 Gulp,在应用程序的构建和发布过程中。
<Target Name="Angular2 Bundle" BeforeTargets="GetProjectWebProperties">
<Message Text="Create Angular2 Bundle from $(DestinationAppRoot)" />
<Exec Command="call gulp buildForProduction"
WorkingDirectory="$(ProjectDir)" />
</Target>
从 Visual Studio 调用 Gulp 只需要编辑项目文件并添加上述 target 标签即可。诀窍在于找到 Gulp 在构建和发布过程中可以执行的时机。为了找出这一点,我配置 Visual Studio 2015 在输出窗口中提供详细信息,以便我可以看到所有被执行的任务。我发现“GetProjectWebProperties
”target 在发布步骤(在构建步骤完成后立即执行)的开始时被执行。一旦我发现了这一点,我只需添加命令“call gulp buildForProduction
”,它就会自动从 Visual Studio 2015 执行。
即使有所有新的工具和开发过程中的 node.js 集成,我真正需要做的就是将打包的 *angular2.min.js* 包含在项目中,并调用 gulp 来使发布过程像以前一样完全无缝,而无需实现任何复杂的工作流程。当然,这并不完美,因为额外的文件(HTML 模板和组件 JavaScript 文件)会被发布到服务器,而这些文件实际上是不需要的,因为应用程序已经打包成一个 JavaScript 文件。也许将来,我会改进这个过程。当然,使用 TFS 和 MsBuild,您可以清理和简化发布过程。
结论
进入 Angular 2 的世界和所有新工具需要消化很多内容。特别是与使用 Angular 2 的候选发布版本合作。很多信息要么稀缺,要么分散,要么随着破坏性更改而迅速过时;因此,您需要深入挖掘才能找到您想要的东西。事实上,信息量如此之大,以至于我不得不将本文分为两部分。本文的第二部分,**《使用 TypeScript 开发 Angular 2 应用程序》**,将深入探讨本文随附的示例应用程序的一些 Angular 2 代码。
历史
- 2016 年 8 月 13 日:初始版本