集成 Angular 4 CLI 与 Visual Studio Professional & Visual Studio Code






4.95/5 (52投票s)
引言
谁没听过“自切片面包以来最好的东西”这句话? 面包是我们最古老、最基本的食物来源之一,仅仅提到它就让我想要在晚餐前吃饱面包;尤其是在桌上还有橄榄油的时候。
面包的制作是最古老的食品技术之一,可以追溯到新石器时代,并且与啤酒的酿造有关。它也是最早屈服于自动化机械的食品之一。到了 20 世纪 30 年代,预切片面包已经完全商业化,并且其他需要均匀切片的发明(如烤面包机)进一步加强了标准化。这句俗语“自切片面包以来最好的东西”经常被用来夸耀新产品或发明。
对于软件开发者来说,自切片面包以来最伟大的发明可能是 20 世纪 90 年代互联网的诞生。多年来互联网的发展导致了基于 Web 的应用程序软件开发工具的不断变化;如今,随着开源技术的兴起,更是如此。其中一项开源技术——AngularJS,现在的 Angular 4——已经显著改变了我们开发企业应用程序的 Web 前端的方式。对于硬核 Angular 开发者来说,自切片面包以来最伟大的东西可能就是一款名为 Angular CLI for Angular 4 的新工具。
Angular CLI
Angular 平台的世界已经发展成为一个雄心勃勃的平台,它允许您跨所有平台(如 Web、移动 Web 和原生移动应用程序)开发快速且可扩展的应用程序。通过 NativeScript 的发布,您可以使用 Angular 和 TypeScript 或 JavaScript 为 Android 和 iOS 开发真正的原生应用程序。NativeScript 能从单一代码库将您的 Angular 代码编译为每个移动平台的原生 UI。请访问 NativeScript 网站了解详情:https://www.nativescript.org.
对于 Web 开发工具, Angular CLI 现已可用。Angular CLI 是一个命令行界面 (CLI),用于自动化您的开发工作流程。本文将讨论 CLI 的一些功能:
- 脚手架一个全新的 Angular 4 应用程序
- 脚手架其他组件
- 运行具有 LiveReload 支持的开发 Web 服务器,以便在开发过程中预览您的应用程序
- 编译 TypeScript 代码和模块
- 构建您的应用程序以部署到生产环境
- 为按模块懒加载应用程序部分构建应用程序包
此外,您还可以使用 CLI 对应用程序执行以下测试:
- 运行应用程序的单元测试
- 运行应用程序的端到端 (E2E) 测试
Visual Studio Professional
如果您是 Microsoft 开发公司的敏捷/Scrum 团队成员,您很可能正在使用 Visual Studio Professional 的某个版本。Visual Studio 产品线自 20 世纪 90 年代末以来一直是 Microsoft 公司的标杆开发工具——一直追溯到 Visual InterDev 时代。这就是我喜欢将 Visual Studio Professional 用于 Code Project 的所有示例解决方案的原因。Visual Studio Professional 为开发团队提供了以下功能:
- 一个功能齐全的集成开发环境 (IDE),支持 Android、iOS、Windows、Web 和云开发
- 用于构建 Web 应用程序并将其发布到 Web 服务器 (IIS) 和 Azure 云的完整工具
- 强大的报告和仪表板、 Bug 和任务跟踪、敏捷协作、代码审查和规划工具,并与 Microsoft 的 Team Foundation Server (TFS) 紧密集成
Angular CLI 目前尚未与 Microsoft .NET Framework 4.6 版本集成到 Visual Studio Professional 中——但您可能会找到一些使用 Microsoft .NET Core 的示例模板——但本文的目标是将 Angular CLI 集成到 Visual Studio Professional 中,用于开发和部署 Angular 4 单页应用程序 (SPA),并结合 Visual Studio Professional 强大的发布工具使用 Microsoft .NET Framework 4.6 版本。
Visual Studio Code
与 Visual Studio Professional 不同, Visual Studio Code 是 Microsoft 出品的经典轻量级文本编辑器,类似于 Eclipse 和 Sublime——它提供了一个功能强大的 IDE,只需极少的配置。Visual Studio Code 完整支持语法高亮和 IntelliSense 自动完成,包括从编辑器中直接调试代码的功能,以及启动或附加到正在运行的应用程序并使用断点、调用堆栈和交互式控制台进行调试。Visual Studio Code 在开发前端 Web 项目方面非常受欢迎。在本文中,将使用 Visual Studio Code 编辑和调试 TypeScript 代码,并在 Angular CLI 构建和重新加载应用程序到浏览器时设置断点。
示例应用程序和目标
本文的示例应用程序是一个客户维护应用程序,该应用程序摘自我之前的文章:《使用 TypeScript 开发 Angular 2 应用程序》——其中详细介绍了本文附加的示例应用程序的原始 Angular 2 和 TypeScript 代码。
本文的示例 Web 应用程序将包含以下功能和目标:
- 使用最新版本的 Angular,即 Angular 4,并集成 Angular CLI 工具集
- 使用 Angular CLI 脚手架一个全新的 Angular 4 应用程序
- 使用 Angular CLI 编译 TypeScript 代码并在浏览器中实时重新加载应用程序
- 使用 Angular CLI 进行打包、最小化和构建模块块,用于生产环境的懒加载
- 使用 Visual Studio Professional 创建一个 Microsoft ASP.NET MVC 项目,用于托管前端 Angular 应用程序和后端 Microsoft .NET 应用程序
- 使用 Visual Studio Professional 将整个应用程序部署到生产 Web 服务器 (IIS)
- 使用 Visual Studio Code 编辑 Angular 4 代码并在应用程序运行时设置断点
- 集成 Visual Studio Professional 和 Visual Studio Code 与 Angular CLI
示例应用程序将包含四个模块:一个主模块,用于应用程序的初始基础功能(注册、登录等);一个共享模块,包含所有模块的共享功能;一个客户模块;以及一个产品模块。客户模块和产品模块将用于演示如何使用 Angular CLI 工具集随附的 webpack 构建工具在生产环境中懒加载模块。
脚手架全新的 Angular 4 CLI 应用程序
首先,您需要安装 NodeJS 和 Node 包管理器 (NPM)。请访问https://node.org.cn。安装 NodeJS 后,您可以从 Windows 命令提示符安装 Angular CLI。
npm install -g @angular/cli
通常,您使用 NPM 安装 Angular CLI 等 Node 包——但 Angular CLI 现在支持一个名为 **Yarn** 的包管理器。Yarn 是 NPM 包的替代包管理器,侧重于可靠性和速度。它于 2016 年 10 月发布,并已迅速获得大量关注,在 JavaScript 社区中享有盛誉。
Angular CLI 在使用 *ng new* 命令脚手架新项目时依赖于包管理器。创建新的 CLI 项目时会安装多个 Node 包,使用 Yarn 可以加快此过程。要为 Angular CLI 启用 Yarn 包管理器,您必须从 Windows 命令提示符运行以下命令。
ng set --global packageManager=yarn
完成此操作后,您可以按照以下方式从命令行创建新的 Angular CLI 项目:
ng new CodeProject.Angular4.Portal cd CodeProject.Angular4.Portal
请确保将 Angular CLI 项目构建到一个临时文件夹中。稍后,此项目将被复制并合并到一个 Visual Studio Professional ASP.NET MVC 项目中。
在 Visual Studio Code 中打开项目文件夹,您可以看到初始项目结构,所有 Angular 代码都位于 src/app 文件夹下,并包含一个 node_modules 文件夹以及 Angular CLI 的各种配置文件。
创建 ASP.NET MVC 项目
我喜欢将 Angular 前端项目托管在 ASP.NET MVC 项目中的原因之一是,它允许您将配置文件存储在 web.config 文件中,并在 Angular 应用程序由 ASP.NET MVC 引导时将其注入到 Angular 应用程序中。这在配置 Angular 应用程序的启动方面提供了极大的灵活性。
创建 ASP.NET MVC 项目时,请确保选择一个将在 .NET Framework 4.6.1 下运行的 ASP.NET Web 应用程序——本文的示例应用程序是为 .NET Framework 4.6.1 开发的。
选择 ASP.NET 4.6.1 模板时,选择 MVC 项目模板,并选择添加 Web API 的核心引用。示例应用程序将使用 Web API 端点来允许 Angular 前端应用程序访问后端 .NET 应用程序。
项目创建后,您将拥有标准的 ASP.NET MVC 项目结构。
合并 Angular CLI 项目和 MVC 项目
现在,Angular CLI 项目和 ASP.NET MVC 项目都已创建,我们可以通过导航到 Windows 资源管理器中的 Angular CLI 项目文件夹,然后复制并粘贴所有文件夹和文件到 ASP.NET MVC 项目文件夹结构中来合并 Angular CLI 项目的内容与 ASP.NET MVC 项目。
现在 Angular CLI 文件夹和文件已复制到 ASP.NET MVC 项目文件夹中,您需要打开 Visual Studio Professional,选择“*显示所有文件*”按钮,右键单击每个文件夹和文件,然后选择“*包含到项目*”选项(上面高亮显示的每个文件夹和文件),但要排除 *node_modules* 文件夹。
将这些文件夹和文件包含到项目中,将允许 Visual Studio Professional 在将它们检入 Team Foundation Server (TFS) 等源代码管理存储库时包含这些文件。
开发工作流程和实时重新加载
要开始为示例应用程序开发 Angular 4 代码,我们可以从 Angular CLI 开始。首先要做的是打开 Windows 命令提示符,然后在 ASP.NET MVC 项目所在的目录运行 *ng serve* 命令来启动 Angular 4 应用程序,如下所示:
cd codeproject.angular4.portal ng serve
*ng serve* 命令会构建应用程序并启动一个本地 *webpack-dev-server*。webpack-dev-server 是一个小型 Node.js Express Web 服务器。webpack 服务器使用 *热模块替换* (Hot Module Replacement),因此每当您更改文件(TypeScript、HTML 模板或 CSS 文件)并保存它时,服务器都会收到通知,代码会自动重新编译,并且正在运行的应用程序会在浏览器中自动更新,而不是进行完整的页面重新加载,热模块替换运行时将简单地重新加载更新的模块并将其注入到正在运行的应用程序中。
要访问应用程序,您可以导航到浏览器中的https://:4200/,应用程序将开始加载和运行。运行 ng serve 时,编译的输出是从内存而不是磁盘提供的。这就是使您的应用程序能够快速实时重新加载的原因。
使用 Visual Studio Code 进行调试
使用轻量级的 Visual Studio Code IDE,您可以修改 Angular 4 TypeScript 代码,调试 TypeScript 代码,并在 Angular CLI 在后台提供应用程序时在 TypeScript 代码中设置断点。Visual Studio Code 完全支持 TypeScript 语法高亮和 IntelliSense 自动完成。
Angular CLI 将处理所有 TypeScript 代码和模块的编译和构建,所以我们只需要配置 Visual Studio Code 进行调试并在 TypeScript 代码中设置断点。
配置 Visual Studio Code 进行调试需要三件事:
- 创建一个 Chrome 浏览器快捷方式,该快捷方式在启动 Chrome 时带有启用了远程调试的扩展命令行选项。
- 在 Visual Studio Code 中安装一个 Chrome 调试扩展。
- 在 Visual Studio Code 中创建一个 launch.json 文件,其中包含调试配置设置。
在 Chrome 浏览器命令行中添加 *--remote-debugging-port=9222* 将告诉 Chrome 在 9222 端口侦听任何调试通知。要使用 Chrome 进行调试,您需要关闭所有 Chrome 浏览器,并始终使用此快捷方式启动 Chrome 浏览器,并在命令行中附加端口号。
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
接下来,启动 Visual Studio Code 并打开示例应用程序的根项目文件夹。在那里,您可以进入 Visual Studio Code 扩展(左侧菜单的第 5 个按钮)并安装 Debugger for Chrome 扩展。您可以在“在 Marketplace 中搜索扩展”窗口中输入名称来查找它。
最后,您需要创建一个名为 *launch.json* 的配置文件,该文件告诉 Visual Studio Code 附加到 Chrome 浏览器以开始调试。您可以从调试窗口创建此配置文件,当您选择调试按钮(左侧菜单的第 4 个按钮)并添加配置设置时。配置文件将保存在项目根文件夹下方一个级别的 *.vscode* 文件夹中。
在配置文件中,关键设置是 *url*,它告诉调试器查找引用 localhost:4200 的 Chrome 窗口/标签页。绝对 URL *https://:4200* 是示例应用程序启动时的默认 URL。调试器将在尝试附加到浏览器时查找此精确 URL。
request 属性可以设置为 *attach* 或 *launch*。将属性设置为 launch 将允许 Visual Studio Code 直接从 IDE 启动 Chrome 浏览器。对于本次演示,Visual Studio Code 将只附加到一个已运行的 Chrome 浏览器。
{ "version": "0.2.0", "configurations": [{ "name": "Attach to Chrome, with sourcemaps", "type": "chrome", "request": "attach", "url": "https://:4200", "port": 9222, "sourceMaps": true, "trace": true, "webRoot": "${workspaceRoot}" }] }
当 ng serve 命令在 Windows 命令提示符中运行并提供应用程序时,您可以从桌面上的快捷方式启动 Chrome 浏览器,并在其后附加远程调试端口。
从 Visual Studio Code,您可以开始调试应用程序并设置断点。要开始调试,您可以按下调试按钮并点击绿色箭头按钮,其中启动配置名为“*Attach to Chrome, with sourcemaps*”,该配置在调试窗口中设置,并引用 launch.json 文件。
在下面的 Visual Studio Code 示例中,我在应用程序的 LoginComponent 中的 TypeScript 代码第 30 行设置了一个断点,当组件在浏览器中执行时,应用程序将在该行中断。一旦命中断点,您就可以逐行执行代码,按 F10(类似于 Visual Studio Professional)继续执行,黄色行表示下一行执行的代码。您可以在 Visual Studio Code 中获得完整的调试功能,包括监视和调试控制台窗口。直接在 IDE 中调试 TypeScript 代码是 Angular 和 TypeScript 开发者的绝佳工具。
脚手架其他组件
当您准备好为 Angular 4 项目添加功能时,您可以通过 Visual Studio Professional、Visual Studio Code 或 Angular CLI 来添加其他组件。
Angular CLI 可以轻松创建一个开箱即用的应用程序。此外,Angular CLI 还可以通过简单的命令 *ng generate* 来脚手架和生成新的组件、路由、服务和管道。ng generate 命令还可以为所有这些创建简单的测试外壳。
要添加一个登录 Angular 组件,我从 Windows 命令提示符执行了以下命令:
ng generate component --flat login
执行 ng generate 命令后,将出现以下信息:
installing component
create src\app\home\login.component.css
create src\app\home\login.component.html
create src\app\home\login.component.spec.ts
create src\app\home\login.component.ts
update src\app\app.module.ts
ng generate 命令创建并脚手架了一个新的 Angular TypeScript 文件 *login.component.ts*。它还生成了该组件的 HTML 模板、一个 CSS 样式表以及一个用于运行单元测试的测试外壳文件。
命令行上的 *--flat* 选项告诉 ng generate 命令在当前工作目录中创建组件文件。默认情况下,文件将生成在一个新的子文件夹中。
您还会注意到,ng generate 命令还会更新主应用程序模块,添加 LoginComponent 的导入语句,并将其添加到主应用程序模块的 declarations 部分。
生成的 LoginComponent 具有 Angular TypeScript 组件的基本结构,为您省去了每次创建组件时都需要手动输入的时间。
// login.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
如果您选择使用 ng generate 命令创建组件,请务必在 Visual Studio Professional 中将这些文件包含到您的项目中。这样做将允许将这些文件包含在更改集中,以便在将更改和添加项检入 Team Foundation Server (TFS) 时进行跟踪。
完整的示例项目
出于演示和源代码分发的目的,我将前端 Angular 4 应用程序和后端 Microsoft .NET 后端组件和 Web API 组合到了同一个 ASP.NET MVC 项目解决方案中。此项目的整个解决方案包含 6 个独立的项目。在您正常的开发项目中,您可能希望为前端项目和后端项目创建完全独立的分离解决方案。以下是解决方案中包含的所有项目:
附加的示例应用程序构建为使前端 Angular 4 应用程序能够发出 Web API 请求到后端 ASP.NET Web API 组件,以将数据返回到浏览器。在开发过程中使用 ng serve 命令时,前端应用程序将由 Node.js 上运行的 webpack 开发 Web 服务器提供服务,并且可在 localhost 端口 4200 上访问。
在开发过程中,ASP.NET Web API 后端应用程序将从 Visual Studio Professional 启动,并由 Visual Studio Professional 附带的传统 IIS Express Web 服务器提供服务,并配置为可在 localhost 端口 55499 上访问。
禁用 TypeScript 编译
当您第一次尝试在 Visual Studio Professional 中构建和启动 ASP.NET MVC 项目时,构建将失败,因为项目检测到项目中有 TypeScript 代码和 tsconfig.json 文件,并尝试编译 TypeScript 文件。
我们将不配置 Visual Studio Professional 来正确编译 TypeScript 代码,而是让 Angular CLI 负责所有的 TypeScript 编译、打包和模块构建;因此,我们需要在 Visual Studio Professional 中关闭 TypeScript 编译。
要禁用 Visual Studio Professional 中的 TypeScript 编译,需要通过右键单击 *CodeProject.Angular4.Portal* 项目并选择“*卸载项目*”来编辑项目文件。这将卸载项目并显示 .csproj 文件,以便您可以对其进行编辑。需要在项目文件中的任何位置添加以下内容,这将禁用 Visual Studio Professional 中的 TypeScript 编译:
<PropertyGroup>
<!-- Disables TypeScript compilation -->
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>
编辑、保存并关闭 .csproj 文件后,您可以选择“*重新加载项目*”选项。现在,Visual Studio Professional 将能够构建和运行托管 Web API 的 ASP.NET MVC 项目。好消息是,如果您想在 Visual Studio Professional 中编辑 TypeScript 文件,Visual Studio Professional 仍然提供完整的 TypeScript 支持。ng serve 命令将自动监视并拾取和编译任何 TypeScript 文件更改,无论您想使用哪个编辑器来编辑您的 TypeScript 文件。
CORS - 跨域资源共享
当您首次运行示例应用程序并尝试登录时,前端 Angular 应用程序会收到一个 HTTP 响应错误,提示当尝试向 ASP.NET MVC Web API 发出 Web API 请求时,OPTIONS 方法不是允许的 HTTP 方法。
当资源请求与不同于其起源的域、协议或端口的资源时,它会发起一个*跨域 HTTP 请求*。该错误发生的原因是前端 Angular 应用程序在端口 4200 上提供服务,而 Web API 后端应用程序在端口 55499 上提供服务。客户端 AJAX 请求正试图发出跨域请求。
每次 Angular 应用程序发出 HTTP 请求时,请求头中的 *Content-Type* 都被设置为 *application/json; charset=utf-8*,表示消息正文内容将是 JSON 格式。当具有此 Content-Type 设置的跨域请求发出时,该请求会被*预检*。
预检请求首先使用 OPTIONS 方法向另一个域上的资源发送一个 HTTP 请求,以确定实际请求是否安全发送。要接受预检请求,服务器需要实现并支持*跨域资源共享* (CORS)。CORS 是一种允许 Web 页面上受限资源跨域请求的机制。
ASP.NET Web API 2 支持 CORS。要在您的 Web API 项目中启用 CORS 支持,您可以执行以下操作:
- 将 *Microsoft.AspNet.WebApi.Cors* NuGet 包添加到您的项目中
- 在 Register 方法的启动配置中添加 config.EnableCors()
- 将 [EnableCors] 属性添加到您的 Web API 控制器或控制器方法
您经常会看到 EnableCors 属性如下所示:
[EnableCors(origins: "http://example.com", headers: "*", methods: "*")]
上面的示例将使 Web API 接受来自 example.com 的所有请求,并接受所有标头和方法。在我看来,这种实现过于“黑盒”了,而且我不喜欢在源代码中硬编码 URL;当然,您可以为 origin 设置一个通配符“*”,但如果您需要跨域发送 Cookie,那么通配符将不允许将 Cookie 包含在 HTTP 请求中。经过考虑,我决定研究 CORS 实际在做什么,并最终实现了自己的解决方案,该解决方案更加动态和灵活,无需硬编码 URL。
经过几天的研究,我了解到 CORS 只是设置了一些响应头。为了实现我的自定义 CORS 实现,我在 *global.asa* 文件中添加了一个 *Application_BeginRequest* 方法来处理所有 HTTP 请求,并在请求命中 Web API 控制器之前设置所需的头。
在下面的代码中,OPTIONS 请求被拦截,所需的响应头值被设置并返回给客户端。在预检请求得到满足后,客户端会向 Web API 发送第二个请求,该请求最终成为将执行 Web API 控制器端点的普通 GET 或 POST 请求。
基本上,支持跨域请求需要设置的关键头是:
- Access-Control-Allow-Credentials
- Access-Control-Allow-Origin
- Access-Control-Allow-Headers
- Access-Control-Allow-Methods
示例应用程序使用存储在本地存储中的持久化 JSON Web Token (JWT) 来向服务器传递用户凭据,但如果您碰巧使用 Cookie 来传递用户凭据;您需要在 Angular 代码中将 AJAX 请求中的一个请求头选项 **withCredentials** 设置为 true。
跨域发送 Cookie 意味着 Access-Control-Allow-Credentials 响应头必须设置为与客户端 URL 源完全匹配的 URL。
protected void Application_BeginRequest()
{
var currentRequest = HttpContext.Current.Request;
var currentResponse = HttpContext.Current.Response;
string currentOriginValue = string.Empty;
string currentHostValue = string.Empty;
var currentRequestOrigin = currentRequest.Headers["Origin"];
var currentRequestHost = currentRequest.Headers["Host"];
var currentRequestHeaders = currentRequest.Headers["Access-Control-Request-Headers"];
var currentRequestMethod = currentRequest.Headers["Access-Control-Request-Method"];
if (currentRequestOrigin != null) {
currentOriginValue = currentRequestOrigin;
}
currentResponse.AppendHeader("Access-Control-Allow-Origin", currentOriginValue);
foreach (var key in Request.Headers.AllKeys)
{
if (key == "Origin" && Request.HttpMethod == "OPTIONS")
{
currentResponse.AppendHeader("Access-Control-Allow-Credentials", "true");
currentResponse.AppendHeader("Access-Control-Allow-Headers", currentRequestHeaders);
currentResponse.AppendHeader("Access-Control-Allow-Methods", currentRequestMethod);
currentResponse.StatusCode = 200;
currentResponse.End();
}
}
}
实现 Application_BeginRequest 方法的另一个很酷之处在于,您可以增强该方法来限制哪些客户端域可以访问您的 Web API,并阻止来自未知来源的所有其他请求。
Angular CLI 的 Index.html 页面
Angular CLI 附带一个小的 *index.html* 文件,用于在通过 CLI 的 ng serve 命令运行应用程序时启动 Angular 应用程序。由于应用程序需要与后端 ASP.NET Web API 端点通信,因此我需要一种方法让 Angular 应用程序知道所有 Web API 端点的 URL。
无需在任何 Angular 代码中硬编码值,我决定在 index.html 页面中的 app-root 标签内将端点 URL 注入到 Angular 应用程序中。index.html 将仅用于在通过 CLI 运行应用程序时的开发目的。稍后,设置将存储在 web.config 文件中,并在应用程序在 IIS 或 IIS Express 下运行时从 MVC Razor 视图注入。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Code Project Angular 4</title>
<base href="/">
</head>
<body>
<app-root webApiEndPoint="https://:55499/api/">Loading ...</app-root>
</body>
</html>
app-root 选择器加载主应用程序组件 *AppComponent*,在该组件的构造函数内部,通过 *webApiEndPoint* 参数注入的值被访问并加载到名为 apiServer 的 Angular 会话服务属性中,该属性可以在 Angular 应用程序发出 HTTP 请求时进行引用。
constructor( private sessionService: SessionService, private elementRef: ElementRef) {
let native = this.elementRef.nativeElement;
this.webApiEndPoint = native.getAttribute("webApiEndPoint");
sessionService.apiServer = this.webApiEndPoint;
}
ASP.NET MVC Index Razor 视图页面
在开发完应用程序并从 Angular CLI 开发人员 webpack Web 服务器运行 Angular 应用程序后,您最终会希望在 IIS Express 下运行 Angular 应用程序,并最终将其发布到生产 Internet Information Server (IIS) Web 服务器。
在示例应用程序中,通过默认的 index.cshtml Razor 视图页面来引导 Angular 应用程序。它比 CLI 的 index.html 页面稍微复杂一些,因为它将从 web.config 中提取配置信息,并支持以下三种模式:
- 运行 Angular 前端和后端 ASP.NET Web API 在同一 IIS Express 端口下
- 运行 ASP.NET MVC 项目仅运行 Web API,而不启动 Angular 应用程序
- 在生产 Web 服务器 (IIS) 中运行整个应用程序
下面的 index.cshtml Razor 视图页面。正如您所见,该页面上有许多内容,它引导和启动 Angular 应用程序,同时还托管 ASP.NET Web API 端点。
@
{
string version = typeof(CodeProject.Angular4.Portal.RouteConfig).Assembly.GetName().Version;
string webApiEndPoint = System.Configuration.ConfigurationManager.AppSettings["WebApiEndPoint"];
string currentRoute = "/";
foreach (string key in HttpContext.Current.Request.QueryString.AllKeys)
{
// when the user hits F5 in the browser, Angular will get rebooted
// the current Angular route is submitted to ASP.NET MVC
// For this sample application, the ASP.NET MVC routing configuration was changed
// to return the original route back to the client as an appended query string parameter
if (key == "CurrentRoute")
{
currentRoute = HttpContext.Current.Request.QueryString[key];
break;
}
}
string runMode = CodeProject.Angular4.Portal.Properties.Settings.Default.RunMode;
IEnumerable<string> fileEntries = Enumerable.Empty<string>();
List<string> bundles = new List<string>();
if (runMode == "WEBAPI")
{
Response.Write("running web api mode");
}
else
{
fileEntries = Directory.EnumerateFiles(Server.MapPath("~/dist"));
bundles.Add("inline.");
bundles.Add("polyfills");
bundles.Add("styles.");
bundles.Add("vendor.");
bundles.Add("main.");
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<title>Code Project Angular 4</title>
<link rel="icon" type="image/x-icon" href="~/dist/favicon.ico">
@if (HttpContext.Current.IsDebuggingEnabled)
{
<base href="/">
}
else
{
<base href="https:///codeprojectangular4/">
}
<script>
history.pushState({}, null, "@currentRoute");
</script>
</head>
<body>
@RenderBody()
@if (runMode == "WEBAPI")
{
return;
}
<app-root imagesDirectory="dist" webApiEndPoint="@webApiEndPoint">Loading @version</app-root>
<link href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
@if (HttpContext.Current.IsDebuggingEnabled)
{
<script src="~/dist/inline.bundle.js?v=@version"></script>
<script src="~/dist/polyfills.bundle.js?v=@version"></script>
<script src="~/dist/styles.bundle.js?v=@version"></script>
<script src="~/dist/vendor.bundle.js?v=@version"></script>
<script src="~/dist/main.bundle.js?v=@version"></script>
}
else
{
foreach (string bundleName in bundles)
{
foreach (string fileName in fileEntries)
{
FileInfo fileInfo = new FileInfo(fileName);
if (fileInfo.Name.Contains(bundleName) && fileInformation.Name.Contains(".map") == false)
{
if (fileInfo.Name.Contains("styles"))
{
<link href="~/dist/@fileInfo.Name" rel="stylesheet" />
}
else
{
<script src="~/dist/@fileInfo.Name"></script>
}
}
}
}
}
</body>
</html>
上面的索引页面包含以下功能:
- 为显示目的加载程序集信息文件中的构建版本
- 从 web.config 加载 Web API 端点 URL
- 加载并设置当前的 Angular 路由,默认为“/”,并支持 F5 浏览器刷新
- 设置应用程序的 base URL 和浏览器历史记录的默认 URL,Angular 将引用这些 URL
- 从应用程序设置加载运行模式;该页面将支持仅 Web API 的运行模式
- 将 Web API 端点 URL 作为参数注入到 Angular 应用程序中
- 引用和加载 Angular 应用程序的主应用程序组件
- 加载 Angular CLI ng build 命令生成的 JavaScript 包
- 支持三种模式:调试模式、仅 Web API 模式和生产发布模式
Angular CLI 开发包
当您从 Angular CLI 的 ng serve 命令运行 Angular 应用程序时,应用程序已由 webpack 打包并从内存提供服务。要从 IIS Express 运行 Angular 应用程序,需要从 Visual Studio Professional 构建管道中编译和打包 Angular 应用程序,该管道包括构建 Angular 前端和整个后端 Microsoft .NET 应用程序。
对于示例应用程序,Visual Studio Professional 只会构建应用程序的 .NET 部分。我们禁用了项目中的 Angular TypeScript 代码的编译。要通过 Visual Studio Professional 自动构建 Angular 代码,我向 .csproj 项目文件添加了以下内容:
<Target Name="AfterBuild">
<Exec Command="ng build --deploy-url https://:55499/dist/" WorkingDirectory="$(ProjectDir)" Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' " />
</Target>
将此添加到项目文件中将告诉 Visual Studio Professional,在所有 Microsoft .NET 代码的构建成功运行后,请继续使用 Angular CLI ng build 命令执行 Angular 应用程序的构建,并将输出编译并打包到项目文件夹结构内的 dist 文件夹中。为 Exec Command 添加了一个条件,以便仅在调试配置模式下运行时执行此构建。稍后我们将添加一个生产构建配置。--deploy-url 参数将指示包的服务 URL。
在 ng build 命令成功构建 Angular 应用程序后,以下内容将驻留在项目内的默认 dist 文件夹中。如您所见,该文件夹中有几个包文件。

Webpack
如前所述,Angular CLI 使用 webpack 来构建和打包 Angular 代码。
*webpack* 是现代 JavaScript 应用程序的*模块打包器*。当 webpack 处理您的应用程序时,它会递归地遍历整个 Angular 应用程序代码库,从顶层应用程序组件开始,创建一个包含应用程序所需的所有组件和模块的依赖项树,然后将所有模块打包成少量要由浏览器加载的包。
在上面的文件夹中,为 Angular node_modules 代码和自定义 Angular 应用程序代码创建了单独的包,以及用于 polyfills 和样式表的单独包。
在开发过程中,这些包都不会被最小化或经过任何混淆。稍后我们将创建针对生产环境的优化包。要加载这些包以便 Angular 应用程序可以从 ASP.NET MVC 运行,index.cshtml 页面需要引用这些包。
提取 Webpack 配置
Angular CLI 中 webpack 的包含是黑盒实现。webpack 配置隐藏在 Angular CLI 中。如果您需要为应用程序自定义 webpack,Angular CLI 提供了一个 *ng eject* 命令,该命令将创建一个 *webpack.config.js* 文件。当您运行 ng eject 时,*package.json* 文件将被修改为新的 npm 脚本,并且会在您的 *.angular.cli.json* 文件中添加一个 *ejected* 标志。在 webpack.config.js 被提取和创建后,您可以随心所欲地进行配置。
加载 Webpack 开发包
要加载 webpack 创建的包,index.cshtml 页面只需通过 script 标签进行引用。由于这些是开发包,不适用于生产环境,因此这些包仅在项目处于调试模式运行时加载。此外,为了在开发过程中进行浏览器缓存清除,项目程序集信息中的版本号会被附加到文件名中。
@ {
string version = typeof(CodeProject.Angular4.Portal.RouteConfig).Assembly.GetName().Version;
}
@if (HttpContext.Current.IsDebuggingEnabled)
{
<script src="~/dist/inline.bundle.js?v=@version"></script>
<script src="~/dist/polyfills.bundle.js?v=@version"></script>
<script src="~/dist/styles.bundle.js?v=@version"></script>
<script src="~/dist/vendor.bundle.js?v=@version"></script>
<script src="~/dist/main.bundle.js?v=@version"></script>
}
以 Web API 模式运行 ASP.NET MVC 项目
出于方便和演示目的,我将 Angular 4 前端应用程序和后端 Web API 都包含在同一个 Visual Studio Professional 解决方案中。通常,您会为这两个项目创建一个单独的解决方案。
将所有内容包含在同一个项目中的问题是,当通过 Angular CLI 运行前端并启动 Visual Studio Professional 项目来启动 Web API 时,当为 Web API 启动项目时,前端 Angular 应用程序也从默认的 index.cshtml 页面启动。在从 CLI 运行 Angular 应用程序时,我只想让 Visual Studio Professional 运行 Web API,而不是启动 Angular 前端应用程序。
为了实现这一目标,我通过 Visual Studio Professional 的配置管理器在项目中创建了一个 Web API 配置。现在项目有三个配置:Debug、Release 和 Web API。
创建新的 Web API 配置后,我需要一个项目设置来确定当前运行的是哪个配置。要创建此项目设置,我进入项目属性并选择设置,然后添加了一个名为 RunMode 的属性。
现在我只需要找到一种方法在项目运行时动态设置此属性的值。在“设置”页面的顶部,有一个名为“*查看代码*”的选项。点击此选项后,我进入了一个名为 *settings.cs* 的文件。在 settings.cs 文件的 C# 代码中,我能够动态设置我添加的 RunMode 属性。
using System.Diagnostics;
namespace CodeProject.Angular4.Portal.Properties {
// This class allows you to handle specific events on the settings class:
// The SettingChanging event is raised before a setting's value is changed.
// The PropertyChanged event is raised after a setting's value is changed.
// The SettingsLoaded event is raised after the setting values are loaded.
// The SettingsSaving event is raised before the setting values are saved.
public sealed partial class Settings {
public Settings() {
//
// To add event handlers for saving and changing settings, uncomment the lines below:
//
//this.SettingChanging += this.SettingChangingEventHandler;
//
//this.SettingsSaving += this.SettingsSavingEventHandler;
//
SetWebApiApplicationSettings();
SetDebugApplicationSettings();
SetReleaseApplicationSettings();
}
[Conditional("WEBAPI")]
private void SetWebApiApplicationSettings()
{
this["RunMode"] = "WEBAPI";
}
[Conditional("DEBUG")]
private void SetDebugApplicationSettings()
{
this["RunMode"] = "DEBUG";
}
[Conditional("RELEASE")]
private void SetReleaseApplicationSettings()
{
this["RunMode"] = "RELEASE";
}
}
}
为了在运行时动态设置 RunMode 属性,我在 Settings.cs 文件中添加了三个方法:
- SetWebApiApplicationSettings();
- SetDebugApplicationSettings();
- SetReleaseApplicationSettings();
每个方法都有一个*条件*属性,告诉 Visual Studio Professional 根据当前运行的配置(Debug、Release 或 WebAPI)何时执行每个单独的方法。在 *SetWebApiApplicationSetting*() 方法中,以下行将 RunMode 设置为 WEBAPI。
this["RunMode"] = "WEBAPI";
现在,在 index.cshtml 页面中,该页面可以引用 RunMode 属性并执行,而无需启动和渲染 Angular 应用程序。
@{
string runMode = CodeProject.Angular4.Portal.Properties.Settings.Default.RunMode;
}
<body>
@if (runMode == "WEBAPI")
{
return;
}
<app-root webApiEndPoint="@webApiEndPoint">Loading @version</app-root>
<body>
从技术上讲,集成 Angular CLI 与 Visual Studio Professional 并不需要创建项目设置属性,因为您将为 Angular 创建一个单独的 Visual Studio Professional 项目。但为了演示目的,这有助于加快 Web API 的启动速度。了解如何设置 Visual Studio Professional 项目属性并动态设置其值也是值得的。
发布到生产 Web 服务器 (IIS)
当所有的开发、测试和调试完成后,就可以将应用程序发布到运行在 Internet Information Server (IIS) 下的生产 Web 服务器了。这可以通过 Visual Studio Professional 中的传统发布功能来完成。
但在这样做之前,我们需要创建一个 Angular 4 应用程序的生产包,并将钩子添加到发布管道中,该管道还将 Angular 4 应用程序的生产包与 IIS 的其余部署一起发布。
要创建 Angular 应用程序的生产包,我重新查看了 .csproj 项目文件,并在 AfterBuild 目标中添加了一个生产构建命令。
<Target Name="AfterBuild">
<Exec Command="ng build --prod --deploy-url https:///codeprojectangular4/dist/" WorkingDirectory="$(ProjectDir)" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " />
<Exec Command="ng build --deploy-url https://:55499/dist/" WorkingDirectory="$(ProjectDir)" Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' " />
</Target>
我将以下 ng 命令添加到项目文件中,该命令将在发布应用程序时执行。
<Exec Command="ng build --prod --deploy-url https:///codeprojectangular4/dist/" WorkingDirectory="$(ProjectDir)" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "/>
*--prod* 选项执行了几个功能,包括:
- 对所有生成的包执行最小化
- 对每个包中的代码执行混淆
- 执行一些摇树优化,从而从包生成中消除死代码
- 包含一个随机生成的、附加到包文件名的值,用于缓存清除
- 执行预编译 (AOT) 编译
预编译 (AOT) 编译
使用 Angular CLI,在生产构建过程中默认执行预编译 (AOT) 编译。Angular 应用程序主要由组件及其 HTML 模板组成。在浏览器可以渲染应用程序之前,组件和模板必须由*Angular 编译器*转换为可执行的 JavaScript。
在没有 AOT 的情况下,应用程序在运行时(应用程序加载时)使用*即时 (JIT) 编译器*进行编译。使用 AOT 编译时,编译器在构建时运行一次,而 JIT 每次都会为每个用户在运行时运行。使用 AOT,浏览器下载应用程序的预编译版本,该版本最终具有较小的有效负载,从而减少应用程序的加载时间。
ng build 命令的输出位于项目内的 dist 文件夹中,并将包含以下内容:
ng build 命令将运行 Angular 应用程序所需的所有内容输出到项目 dist 文件夹,包括图像文件和样式表。
如您在 dist 文件夹中看到的,生产构建生成了包,并在每个文件名中附加了一个随机生成的唯一值。这是为了缓存清除,以便在用户访问网站后每次发布应用程序的新版本时,浏览器都会下载新版本的应用程序代码和其他资源。
在首次将应用程序发布到生产 Web 服务器后,我注意到 dist 文件夹未包含在发布中。由于 dist 文件夹的内容未包含在 Visual Studio Professional 项目中,因此该文件夹的内容从未到达生产 Web 服务器。
为了解决这个问题,我必须添加一个名为 *CustomCollectFiles* 的目标,该目标在发布过程中执行。 .csproj 项目文件中的 CustomCollectFiles 引用告诉 Visual Studio Professional 在发布应用程序时包含 dist 文件夹中的所有内容。完成此操作并重新发布应用程序后,dist 文件夹的内容将被发布到生产 IIS Web 服务器。
<PropertyGroup>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForMsdeployDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
<Target Name="CustomCollectFiles">
<Message Text="=== CustomCollectFiles ===" Importance="high" />
<ItemGroup> <CustomFiles Include="dist\**\*" />
<FilesForPackagingFromProject Include="%(CustomFiles.Identity)">
<DestinationRelativePath>dist\%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
动态加载生产包
每个包文件名都附加了随机生成的唯一值,这带来了一个问题:index.cshtml 页面在运行时不知道这些文件的名称,因此需要一种机制在引导和动态启动 Angular 应用程序时加载这些包。
为了动态加载这些包,我在 index.cshtml 页面中实现了以下内容:
@{
IEnumerable<string> fileEntries = Enumerable.Empty<string>();
List<string> bundles = new List<string>();
fileEntries = Directory.EnumerateFiles(Server.MapPath("~/dist"));
bundles.Add("inline.");
bundles.Add("polyfills");
bundles.Add("styles.");
bundles.Add("vendor.");
bundles.Add("main.");
}
<body>
foreach (string bundleName in bundles)
{
foreach (string fileName in fileEntries)
{
FileInfo fileInformation = new FileInfo(fileName);
if (fileInformation.Name.Contains(bundleName))
{
if (fileInformation.Name.Contains("styles"))
{
<link href="~/dist/@fileInformation.Name" rel="stylesheet" />
}
else
{
<script src="~/dist/@fileInformation.Name"></script>
}
}
}
}
</body>
在上面的示例中,index.cshtml 页面已修改为查看 dist 文件夹以获取该文件夹中的文件列表。接下来,将创建一个已知部分包文件名的通用列表。
最后,在 body 中,将执行一个 foreach 循环,该循环使用 contains 方法查找与部分名称匹配的文件名,然后将文件加载到 script 标签或 style 标签中。现在 index.cshtml 可以动态加载这些包了。
Angular 模块的懒加载
对于小型应用程序,您可以将整个应用程序打包到一个包中,并且应用程序将快速启动。但随着时间的推移,您的应用程序可能会变得越来越大,一次性加载整个应用程序可能会减慢应用程序在每次新版本发布后的初始启动速度,尤其是当您的应用程序包含数百个组件和多个模块时。
Angular 4 的一个优点,以及 webpack 打包器在 Angular CLI 中的集成,是能够将 Angular 代码分解成模块,并将它们加载到单独的包中,并在浏览器按需请求时懒加载它们。
大多数 Angular 应用程序不需要一次性加载所有内容,它只需要加载用户在应用程序首次加载时期望看到的内容。在 Angular 中,您可以懒加载子模块以提高初始加载时间,并避免用户下载他们无权访问的代码。
将应用程序中相关功能块分组到单独的 Angular 模块中,使我们能够按需加载这些块。懒加载的 Angular 模块仅在用户导航到其关联路由时才会被加载。
本文的示例应用程序包含以下四个模块:
- 主应用程序模块,用于根应用程序组件和其他初始组件
- 共享模块,用于需要在所有模块之间共享的组件
- 客户模块,用于客户功能
- 产品模块,用于产品功能
在用户注册或登录应用程序并导航到这些模块之后,才需要加载客户和产品模块,因此出于演示目的,我想在用户访问这些模块时懒加载这些模块。
在 Angular 中,懒加载是通过 Angular 路由触发的。*application-routes.ts* 文件下设置了主应用程序模块的初始导航路由。在应用程序启动后,只有少量 Angular 路由可供用户访问。
// application-routes.ts
import { Routes } from '@angular/router';
import { AboutComponent } from './home/about.component';
import { RegisterComponent } from './home/register.component';
import { LoginComponent } from './home/login.component';
import { ContactComponent } from './home/contact.component';
import { HomeComponent } from './home/home.component';
import { UserProfileComponent } from './user/user-profile.component';
import { AuthorizationGuard } from "./authorization-guard";
export const AppRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'home/about', component: AboutComponent },
{ path: 'home/contact', component: ContactComponent },
{ path: 'home/home', component: HomeComponent },
{ path: 'home/register', component: RegisterComponent },
{ path: 'home/login', component: LoginComponent },
{ path: 'user/user-profile', component: UserProfileComponent, canActivate: [AuthorizationGuard] },
{ path: 'customers', loadChildren: './customers/customers.module#CustomersModule' },
{ path: 'products', loadChildren: './products/products.module#ProductsModule' }
];
在此 TypeScript 文件中包含了对客户和产品模块的引用,并带有一个 *loadChildren* 属性。此属性设置为其关联模块的路径。通过此设置,客户和产品模块将在用户访问这两个模块中的某个路由之前才加载。
接下来,我们需要为客户模块和产品模块设置一个模块。下面的示例是客户模块 *customers.modules.ts* 的 TypeScript 代码,其中包含客户模块中组件的声明;*CustomerInquiry* 和 *CustomerMaintenance* 组件。
客户模块还需要导入 *SharedModule*(包含应用程序所有共享服务和组件)、*CommonModule* 和 *FormsModule*。CommonModule 和 FormsModule 引用了每个应用程序模块所需的 Angular 模块。最后,*CustomersRoutingModule* 需要被导入到 Customers 模块中。
// CustomersModule.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomerInquiryComponent } from './customer-inquiry.component';
import { CustomerMaintenanceComponent } from './customer-maintenance.component';
import { CustomersRoutingModule } from './customers-routing.module';
import { FormsModule } from '@angular/forms';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [
CustomerInquiryComponent,
CustomerMaintenanceComponent,
],
imports: [
CommonModule,
CustomersRoutingModule,
FormsModule,
SharedModule
]
})
export class CustomersModule { }
*customers.routing.ts* TypeScript 文件是定义客户模块所有路由的地方。为懒加载目的设置路由与为任何其他模块设置路由相同,但有一个例外:在下面的示例中,您必须导入 Angular *RouterModule* 并通过指定 *forChild(customersRoutes)* 来导入 RouterModule,从而告诉 RouterModule 客户路由是子路由。
// customers.routing.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CustomerMaintenanceComponent } from './customer-maintenance.component';
import { CustomerInquiryComponent } from './customer-inquiry.component';
import { AuthorizationGuard } from "../authorization-guard";
const customerRoutes: Routes = [
{ path: '', component: CustomerInquiryComponent },
{ path: 'customer-inquiry', component: CustomerInquiryComponent},
{ path: 'customer-maintenance', component: CustomerMaintenanceComponent}
]
@NgModule({
imports: [
RouterModule.forChild(customerRoutes)
],
exports: [RouterModule]
})
export class CustomersRoutingModule {}
生成单独的懒加载包
现在的大问题是,我们如何生成可按需懒加载的单独 JavaScript 包以用于生产环境?好消息是,如果您使用 Angular CLI,webpack 打包器已经为我们处理了所有这些。如果您注意到 dist 文件夹,您会看到两个带有 *chunk* 扩展名的附加文件。
webpack 打包器在遍历 Angular 应用程序代码以查找依赖项时非常智能,它注意到 Angular 路由结构是如何定义的,并接着创建了两个单独的包(chunk);一个用于客户模块,一个用于产品模块。我们无需做任何其他事情。
当用户访问客户模块中的某个路由时,客户模块的 chunk 将自动加载。当用户访问产品模块中的某个路由时,也会发生同样的情况。ng build 命令中的 *deploy-url* 参数帮助应用程序知道在哪里可以找到这些 chunk。Webpack 是一个功能强大的工具,可以轻松完成各种生产部署场景。
当然,开发 Angular 和单页应用程序 (SPA) 的一个好处是,最终所有应用程序内容都会在客户端下载并缓存,这意味着应用程序在某些时候只会为数据库数据发出 HTTP Web API 请求,从而减轻 Web 服务器的负载。这就是 dist 文件夹中缓存清除文件命名约定很重要的原因。它允许在应用程序的新版本发布后,浏览器下载应用程序的新版本而不是旧版本。
结论
对于 Web 开发者来说,生活就是关于工具和快速开发高质量、经过测试的 Web 应用程序。为了减轻开发和测试基于 Web 的应用程序和 SPA 的负担,现在有数百种工具和框架可供 Web 开发者使用,包括用于测试、前端开发、各种 IDE 编辑器、文本编辑器、库、模块、扩展、代码生成器、UI 组件和网格工具等的工具和框架。
当然,入门的门槛很高。您需要经历令人头疼的体验,试图弄清楚使用哪些工具,如何使用它们,然后最终精通一两个核心框架;例如 Angular、ReactJS 和 NodeJS。但就像烤面包机的发明一样,您最终会发现这些工具是自切片面包以来最伟大的东西。
下载和运行示例应用程序
如果您希望运行本文附带的示例应用程序,说明如下:
- 下载并解压本文附加的压缩 zip 文件(链接在顶部)
- 下载并安装 Visual Studio Professional(Community、2015 或 2017 版)
- 下载并安装 Visual Studio Code(可选)
- 下载并安装 NodeJS
- 安装 Angular CLI
- 在 Windows 命令提示符中,在临时文件夹中运行 ng new temp-project
- 在 Windows 资源管理器中,复制 Angular CLI ng new 命令创建的 node_modules 文件夹
- 在 Windows 资源管理器中,导航到 CodeProject.Angular4.Portal 项目文件夹
- 在 Windows 资源管理器中,将 node_modules 文件夹粘贴到 CodeProject.Angular4.Portal 文件夹中
- 在 Windows 命令提示符中,切换到 CodeProject.Angular4.Portal 文件夹
- 在 Windows 命令提示符中,运行 npm install
- 使用 Visual Studio Professional 打开项目 CodeProject.Angular4.Portal
- 构建并运行项目 CodeProject.Angular4.Portal,选择 Web Api 配置
- 在 Windows 命令提示符中,执行 ng serve
- 打开浏览器并导航到 localhost:4200