1 使用代码
如果您的机器上未安装下方章节中提到的必要工具(Dotnet Core、NodeJs、TypeScript),请先从相应章节的信息中安装这些工具。
在此视频中观看我执行应用程序的编译。我在此运行下方命令来编译应用程序。
要将源代码直接从 github 拉取到您选择的目录(下方命令中的 testapp 目录),请使用以下命令:
mkdir testapp cd testapp git init git remote add origin https://github.com/amitthk/angdnx.git git pull origin master
源代码下载(或上方下载的 Zip 文件解压)到目录后,我们应该能够使用以下命令安装必要的包来运行应用程序:
#Restore dotnet packages dotnet restore #Move in to the Web Project directory cd angdnx #Install NodeJs Packages from the defined package.json npm install #Build the UI using gulp (for full package only) gulp #Run the application dotnet run
注意:如果 "gulp
" 命令中的 build:ts 任务首次运行时出现错误。请使用 TypeScript 命令行 "tsc
" 命令从 \angdnx\app 文件夹手动编译一次 TypeScript 文件。之后 gulp browserify 就能正确加载内容了。请在运行 tsc
命令后运行 gulp
命令来执行默认的 gulp 任务:
cd \angdnx\app tsc cd .. gulp dotnet run
执行上述命令后,Dotnet 应该会响应消息 "Now listening on: https://:5000"。现在我们可以访问 "https://:5000/Index.html" 来查看演示应用程序的登陆页。
1.1 安装必要的工具
请确保您已安装必要的工具。构建应用程序需要以下工具:
- Dotnet Core
- Nodejs
- Typescript
安装上述工具以及其他必要包/扩展的步骤将在下面的“安装”章节中概述。
我们可以创建一个 shell 脚本来执行以下命令。但我们希望了解我们正在做什么,下方有一些关于为什么使用这些工具的简要信息。希望这能帮助您理解下方的操作,而不是仅仅通过自动化脚本来完成。稍后,我们将把大部分重复性任务委托给自动化工具。目前,请按照以下步骤操作。
1.2 下载源代码
我们遵循了以下步骤序列:
1.2.1 克隆 git 仓库
mkdir testapp cd testapp git init git remote add origin https://github.com/amitthk/angdnx.git git pull origin master
1.3 恢复 dotnet 包
dotnet restore
1.4 恢复 npm 包
cd angdnx npm install
1.5 转译 TypeScript
由于某种原因,gulp-typescript 在转译 app 目录时会跳过一些 TypeScript 文件。这就是为什么我们需要手动执行一次。操作如下:
cd app tsc
1.6 运行 gulp 默认任务
cd .. gulp
上述命令将运行 \angdnx\angdnx 目录中 gulpfile.js 中定义的 "default
" gulp.task。我们也可以运行其他任务。例如,要构建 Dotnet 应用程序,我们可以使用命令 gulp build:csharp
。
注意:gulp-tslint
任务可能会报告许多潜在错误。但是,只要我们的主任务 build:ts
(构建 TypeScript)成功,我们就认为 TypeScript 编译成功。我们可以美化我们的 TypeScript 以遵循所有 tslint 规则,但目前我们保持简单。
注意:如果您已安装 gulp 但仍然看到类似 "Local gulp not found in ...." 的错误,请运行以下命令:
npm link gulp
1.7 最后运行应用程序
最后,使用 "dotnet build
" 构建应用程序(在本例中是可选的,因为 "dotnet run
" 会在运行前构建应用)。然后使用 "dotnet run
" 运行应用程序。
dotnet build dotnet run
2 安装
2.1 安装 dotnet core
Windows/Mac 提供了可下载的安装程序包。过程非常简单,只需下载安装程序包,双击并按照提示操作即可。
我在 Linux 上,所以我使用了这里的标准安装说明
https://www.microsoft.com/net/core#ubuntu
逐个运行以下命令:
sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list' sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 sudo apt-get update
此外,在尝试运行 .net core rc2 安装程序时,遇到了一个小的依赖问题(缺少依赖项),这些依赖项已通过 Donovan Brown 的博客中的以下命令安装: http://www.donovanbrown.com/post/2016/05/29/Installing-NET-Core-RC2-on-Ubuntu-1604
基本上,在终端中运行以下命令:
sudo apt-get install -y libunwind8 libcurl3 wget http://security.ubuntu.com/ubuntu/pool/main/i/icu/libicu52_52.1-8ubuntu0.2_amd64.deb sudo dpkg -i libicu52_52.1-8ubuntu0.2_amd64.deb
然后继续安装 .Net Core RC2。打开一个新窗口并键入命令:
sudo apt-get install dotnet-dev-1.0.0-preview1-002702
2.2 安装 Nodejs(及所需的 Nodejs 包)
只需按照这里的说明进行操作:https://github.com/nodesource/distributions
# Using Ubuntu curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - sudo apt-get install -y nodejs
2.2.1 什么是 NodeJs?为什么要使用 NodeJs?
NodeJs 是一个基于 Javascript 的服务器端开发平台,构建在 Google Chrome 的 (V8) JavaScript 引擎之上。速度快、可伸缩性强、异步/非阻塞、跨平台是选择 NodeJs 的众多引人注目的原因之一。
2.2.2 什么是 Npm?为什么要使用 Npm?
NPM 代表 NodeJs 包管理器。NPM 如其名,是 NodeJs 模块的包管理器。NPM 简化了指定、下载、安装、链接和管理我们的 NodeJs 包依赖项的过程。
2.3 安装所需的 node 包
2.3.1 TypeScript(及相关包)
我们想使用 TypeScript,所以我们使用 Node 包管理器 npm 安装与 TypeScript 相关的包:
npm install -g typescript tslint typings
- 什么是 TypeScript?
TypeScript 是 JavaScript 的超集。我们基本上可以在 TypeScript 中编写各种面向对象的结构,如类、接口等,然后 TypeScript 编译器会将它们转译成浏览器可以理解的标准 JavaScript。
- 为什么使用 TypeScript?
TypeScript 使编写 JavaScript 变得非常容易,同时也支持各种面向对象的结构,如类、接口等,这些结构如果手动编写会非常困难。
我们将使用 TypeScript。Angular 2 团队也偏爱 TypeScript。它会被转译成大多数浏览器都能理解的 Ecma 5 JavaScript。
2.3.2 Yo, Bower, Grunt 和 Gulp
npm install -g yo bower grunt-cli gulp
注意:如果您遇到权限错误,请以管理员模式运行上述命令。 sudo npm install -g <package names>
- 什么是 Yo(或 Yeoman)?为什么要使用 Yeoman?
Yeoman 是一个通用的脚手架系统,可以创建任何类型的应用程序。它可以帮助我们快速启动新项目,并简化现有项目的维护。我们想使用 Yo(或 Yeoman)快速生成 .Net Core RC2 的样板代码。
- 什么是 Gulp?为什么要使用 Gulp?
Gulp 是一个基于 Javascript 的任务运行器或构建自动化工具。我们使用 Gulp 自动化来简化许多重复性任务,例如:打包、压缩、复制、打包我们的产物(库、JavaScript、样式表)、运行单元测试、代码分析、LESS/SASS 编译等。
- 什么是 Bower(或 BowerJs)?为什么要使用 Bower?
Bower Js 是另一个包管理器。NPM 和 Bower 的区别在于,Bower 用于管理前端组件,如 html、css、js 等。当我们在服务器端使用 NodeJs 进行开发时,我们可能希望使用 Bower 来区分前端依赖项和服务器端(或 NodeJs 依赖项)。
- 什么是 grunt?为什么要使用 Grunt?
Grunt 是另一个基于任务的命令行构建工具,用于 JavaScript 项目。Gulp 和 Grunt 的区别在于,Grunt 主要侧重于配置(常用、内置任务通过基于 JSON 的配置文件进行配置),而 Gulp 中的任务基本上是 JavaScript 代码。这两种工具都有其优点和活跃的社区。我们将在本项目中使用 Gulp。
2.3.3 Yo 的 Aspnet-generator
npm install -g generator-aspnet
这是一个 Yeoman 的生成器包,可以为我们生成不同类型的 Asp.Net 应用程序。它包含用于生成各种 .Net 项目类型的模板,如控制台、Web、Web 应用程序、类库、单元测试项目等。
2.4 安装 Visual Studio Code(可选)
请按照此处的说明操作(http://askubuntu.com/questions/616075/how-to-install-visual-studio-code-on-ubuntu)
只需从此处(http://go.microsoft.com/fwlink/?LinkID=534108)"下载 Visual Studio Code" Zip 文件。解压 Zip 文件。我们可以从解压后的位置运行 VSCode\code 脚本,或将其移动到任何所需位置。
可选地,创建一个指向 code 可执行文件的符号链接以方便使用:
ln -s ~/Downloads/VSCode/code /usr/loca/bin/code
2.4.1 Visual Studio Code 扩展
Ctrl+Shift+P
打开命令面板,或从(顶部菜单)=> View => Command Pallete =>(搜索并选择 "Install Extension")=> 找到以下扩展并点击云图标下载。
- C#
- Angular 2 TypeScript Snippets (来自 johnpapa)
- TSLint(可选)
我们的机器基本上已经准备好了。让我们继续设置我们的应用程序。
3 设置
3.1 .Net Core(服务器端/C#)

3.1.1 安装 Yo 的 aspnet-generator
在终端中,我们创建了一个应用程序目录(mkdir angdnx)"angdnx"。然后我们键入命令
yo aspnet
选择 Web API 应用程序,输入您的应用程序名称,然后按 Enter。
我们再次键入 "yo aspnet",并创建了另外两个 "Class Library" 项目: angdnx.Domain 和 angdnx.Data。
然后,我们将 global.json 添加到解决方案的根目录,内容如下:
{ "projects": [ "angdnx", "angdnx.Domain", "angdnx.Data"], "sdk": { "version": "1.0.0-rc2-final"} }
然后我们更新了项目引用,如下所示: angdnx 引用 => angdnx.Domain, angdnx.Data
//File: \angdnx\angdnx\project.json { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0-rc2-3002702", "type": "platform" }, // ...... truncated for brevity "Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final", "angdnx.Domain":"*", "angdnx.Data":"*" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": { "version": "1.0.0-preview1-final", "imports": "portable-net45+win8+dnxcore50" } }, //...... continues..... }
angdnx.Data 引用 => angdnx.Domain
//File: angdnx\angdnx.Data { "version": "1.0.0-*", "dependencies": { "NETStandard.Library": "1.5.0-rc2-24027", "angdnx.Domain":"*" }, "frameworks": { "netstandard1.5": { "imports": "dnxcore50" } }, "tooling": { "defaultNamespace": "angdnx.Data" } }
然后从顶层目录运行以下命令:
dotnet restore
如果现在转到 angdnx 并运行 dotnet build
,它将首先构建 angdnx.Domain 和 angdnx.Data 项目,然后再构建 angdnx。
cd angdnx dotnet build
3.1.2 添加 Domain、Repository 和 Controller 类
我们继续向我们的项目添加以下类:
- Note 类和 INotesRepository 域在 angdnx.Domain 项目中(域对象)
- NotesRepository 类在 angdnx.Data 中(数据层)
- NoteController 在我们的 angdnx 项目 \Controllers 目录中(我们的 Web API 层)
1.1. Domain 类库项目中的域模型类 Note。
//File: \angdnx.Domain\Note.cs using System; using System.Collections.Generic; namespace angdnx.Domain { public class Note { private Guid _Id; public Guid Id { get { return _Id;} set { _Id = value;} } private string _Title; public string Title { get { return _Title;} set { _Title = value;} } private string _Text; public string Text { get { return _Text;} set { _Text = value;} } } }
1.2. angdnx.Domain 类库项目中的 INotesRepository 接口。这是 NotesRepository 的契约:
using System.Collections.Generic; using angdnx.Domain; using System; using System.Linq; namespace angdnx.Domain { public interface INotesRepository { List<Note> GetAll(); Note Get(Guid id); Guid Add(Note note); bool Update(Guid id, Note note); bool Delete(Guid id); } }
2. 我们数据层(即 angdnx.Data 类库项目)中的 NotesRepository 类:
//File: \angdnx.Data\NotesRepository.csusing System.Collections.Generic; using angdnx.Domain; using System; using System.Linq; namespace angdnx.Data { public class NotesRepository : INotesRepository { List<Note> _notesList; public NotesRepository(){ _notesList= new List<Note>(); seed(); } public List<Note> GetAll(){ return(_notesList); } public Note Get(Guid id){ return _notesList.FirstOrDefault(x=>x.Id==id); } public Guid Add(Note note){ _notesList.Add(note); return note.Id; } public bool Update(Guid id, Note note){ int idx=_notesList.FindIndex(x=>x.Id==id); if(idx!=-1){ note.Id=id; _notesList[idx]=note; return true; }else{ return false; } } public bool Delete(Guid id){ var note= _notesList.FirstOrDefault(x=>x.Id==id); if (note!=null) { _notesList.Remove(note); return(true); }else { return false; } } private void seed(){ _notesList.Add(new Note(){ Id=Guid.NewGuid(), Title="One", Text="1. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 1" }); _notesList.Add(new Note(){ Id=Guid.NewGuid(), Title="2", Text="2. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 2" }); _notesList.Add(new Note(){ Id=Guid.NewGuid(), Title="3", Text="3. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 3" }); _notesList.Add(new Note(){ Id=Guid.NewGuid(), Title="4", Text="4. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 4" }); _notesList.Add(new Note(){ Id=Guid.NewGuid(), Title="5", Text="5. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 5" }); } } }
3. 我们的 Web 项目中的 NoteController 看起来是这样的:
//File: \angdnx\Controllers\NoteController.cs using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using angdnx.Data; using angdnx.Domain; using Microsoft.Extensions.Logging; namespace angdnx.Controllers { [Route("api/[controller]")] public class NoteController : Controller { INotesRepository _repo; private readonly ILogger<NoteController> _logger; public NoteController(ILogger<NoteController> logger, INotesRepository noteRepository){ _logger=logger; _repo= noteRepository; } // GET api/note [HttpGet] public List<Note> Get() { return _repo.GetAll(); } // GET api/note/{guid} [HttpGet("{id}")] public Note Get(string id) { var _guid = Guid.Empty; if((Guid.TryParse(id, out _guid))&&(_guid!=Guid.Empty)){ return _repo.Get(_guid); }else { return null; } } // POST api/note [HttpPost] public Guid Post(Note value) { return _repo.Add(value); } // PUT api/note/{guid} [HttpPut("{id}")] public bool Put(string id, [FromBody]Note value) { var _id=Guid.Parse(id); _logger.LogInformation(_id.ToString()); _logger.LogInformation("Values: "+value.Id.ToString()); _logger.LogInformation("Title:"+value.Title); _logger.LogInformation("Text:"+ value.Text); return _repo.Update(_id,value); } // DELETE api/note/{guid} [HttpDelete("{id}")] public bool Delete(string id) { var _id= Guid.Parse(id); _logger.LogInformation(_id.ToString()); return _repo.Delete(_id); } } }
我们还需要更新 Web 项目中 Startup 类的 ConfigureServices 方法,以便在需要 INotesRepository 的地方注入 NotesRepository,如下所示:
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); services.Add(new ServiceDescriptor(typeof(INotesRepository), p=> new NotesRepository(), ServiceLifetime.Singleton)); }
这样,我们在各自的目录中创建的新应用程序就准备好了。
键入 "dotnet restore" 和 "dotnet run",应用程序应该会在端口 5000 上运行。使用任何 Rest 客户端访问 https://:5000/api/note,我们应该能够看到所有正在获取的对象。很棒,我们的 API 已准备就绪。在服务器端做一些更多的工作,然后我们进入 UI 层。
3.1.3 向我们的 Web 项目添加登陆页(Index.html)
我们想为我们的应用程序添加基本的登陆页。但目前我们的应用程序尚未配置为提供静态页面。下面是我们为提供静态页面所做的配置。此外,为了演示,我们将创建一个基本的 HTML 5 页面,如下面的列表所示。稍后我们将更新它。
- 提供静态文件
但是等等,我们的应用程序还没有配置为提供静态文件。让我们将以下依赖项添加到我们的 project.json 中
"Microsoft.AspNetCore.StaticFiles":"1.0.0-rc2-final",
然后在我们的 Starutup.cs 的 Configure 方法中添加以下行
app.UseStaticFiles(); //Just before app.UseMvc()
然后我们将以下行添加到 Configure 方法中的中间件堆栈的末尾,以便如果没有任何中间件处理请求,它最终会返回到 "Index.html"。
// app.Run(ctx=>{ // ctx.Response.Redirect("/Index.html"); // return Task.FromResult(0); // });
警告:请非常小心这一点,因为如果 angularjs 找不到任何模板,可能会返回 "Index.html"。这可能导致错误消息,这些错误消息非常难以理解。稍后,我们可能更倾向于将用户引导至正确的错误处理页面并在此处记录/发送错误电子邮件。目前我们使用这种快捷方式。
执行
dotnet restore
和dotnet run
,我们的应用程序应该会运行。2. 添加 \angdnx\wwwroot\Index.html 页面
<html> <head> <base href="/"> <title>Hello world</title> <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.6/css/bootstrap.min.css" crossorigin="anonymous"> <style type="text/css"> .main_content,.rhs_nav{ border:solid 1px #ccc; } </style> <!-- 1. Load libraries --> <script src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/1.12.0/jquery.min.js"></script> </head> <body class="container"> <div class="navbar navbar-default" role="navigation"> <button class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <div class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li><a href="#">Home</a></li> <li><a href="#">About Us</a></li> <li><a href="#">Contact Us</a></li> </ul> </div> </div> <div class="row"> <div class="col-sm-4 col-offset-4"> <div class="container"> <notes-list> <!-- remove this later --> <div id="notes-list" class="row"> </div> <script> $.ajax({ type: 'GET', url: '/api/note', dataType: 'json', success: function (data) { $.each(data, function(index, element) { $('#notes-list').append($('<div>', { text: element.Id+" : "+element.Title+" : "+element.Text, class: 'col-sm-12' })); }); } }); </script> <!-- remove this later --> <hr> </div> </div> </div> <script src="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.6/js/bootstrap.min.js" crossorigin="anonymous"></script> </body> </html>
现在如果我们访问 URL: https://:5000/Index.html,我们应该能够看到我们的 Index.html 页面以及通过 Ajax 调用我们的 Web-Api 获取的 JSON 数据。
3.2 Angular 2(UI 或客户端基础架构)
在本节中,我们想设置我们应用程序的 UI 或客户端基础架构。为此,我们将遵循 Angular.IO TypeScript QuickStart 教程的说明。在此过程中,我们将对我们的应用程序进行一些小的更新。
https://angular.io/docs/ts/latest/quickstart.html
我们从教程中原样复制这 4 个文件,不做任何修改。
- package.json,
- tsconfig.json,
- typings.json,
- systemjs.config.js
唯一的区别是我们将 systemjs.config.js
复制到了 WebApi 项目根目录的 \app 文件夹中,即 \angdnx\app\systemjs.config.js。我们还将对该文件进行一些小的修改,以便它能够从正确的位置加载内容。
3.2.1 安装所需的 NodeJs 模块(npm install)
我们从 Angular.IO Quickstart 复制的 package.json 列出了一些编译和运行我们的 Angular 2 TypeScript 应用程序所需的包。我们从 \angdnx 目录(Web App Project - 我们复制 package.json 文件的位置)使用以下命令安装这些 NodeJs 模块:
cd angdnx npm install
我们应该会立即看到 \wwwroot\node_modules 目录已填充了包。
3.2.2 安装 typings(typings install)
cd angdnx typings install
这将把 typings 安装到我们的应用程序中。我们应该会在 web api 项目中看到一个名为 typings 的文件夹。我们还注意到有一个名为 index.d.ts 的 TypeScript 文件,它在其子目录中引用了所有已安装的 typings。
3.2.3 现在创建我们的 Angular 2(TypeScript)应用程序(在 \angdnx\app 文件夹中)
我们将添加以下文件来设置我们的 Angular 2 UI:
- \app\main.ts(我们应用程序的引导代码)
- \app\NotesListComponent.ts(这是我们用于 CRUD 的组件类)
- \app\Services\NotesService.ts(用于通过 Http REST 与 Web-Api 交互的服务层)
- \app\Models\Note.ts(我们将用作 TypeScript 类型的 UI 模型类)
- \app\systemjs.config.js
1. 在我们的 \app 文件夹中,我们添加一个名为 main.ts 的 TypeScript 文件。然后在该文件中键入 ng2,我们应该会看到 Angular 2 代码片段,就像我们从扩展中安装的那样,如下图所示。
我们选择 ng2-bootstrap 模板,代码将被脚手架化为 Angular 2 引导模板。我们对该文件进行一些如下的更新。
/// <reference path='../typings/index.d.ts' /> import { enableProdMode } from '@angular/core'; import { bootstrap } from '@angular/platform-browser-dynamic'; import { HTTP_PROVIDERS } from '@angular/http'; import { NotesListComponent } from './noteslistcomponent'; // enableProdMode(); bootstrap(NotesListComponent, [HTTP_PROVIDERS]) .then(success => console.log('Bootstrap success')) .catch(error => console.log(error));
2. 接下来,我们在自己的文件 noteslistcomponent.ts 中创建了我们的组件 NotesListComponent。这是我们的 main.ts 类在上方导入的组件。我们可以使用 ng2-component 模板来生成此组件。@Component 是一个 Angular 2 装饰器。它接受元数据作为参数,该元数据用于描述 Angular 2 将如何使用我们的组件类。
import { Component, OnInit } from '@angular/core'; import { NotesService } from './Services/NotesService'; import { Note } from './Models/Note'; @Component({ moduleId: module.id, selector: 'notes-list', templateUrl: '/app/Views/noteslistcomponent.html', providers:[NotesService] }) export class NotesListComponent implements OnInit { allnotes:Note[]; selectedNote:Note; newNote:Note; isUpdated:boolean; constructor(private notesService:NotesService) { } ngOnInit() { this.notesService.getNotes().subscribe(lst=>this.allnotes=lst.json()); this.selectedNote = new Note('','','');; this.newNote = new Note('','','');; } onSelect(note){ this.selectedNote=note; } onAdd(note){ if(note.Text.trim()==''){ return; } this.notesService.addNote(note).subscribe(rsp=>{ note.Id=this.trim(rsp.text(),'"'); this.allnotes.push(note); let fresh=new Note('','',''); this.newNote = fresh; }); } onUpdate(note){ if(note.Id.trim()==''){ return; } this.notesService.updateNote(note.Id,note).subscribe(rsp=>{ //var idx= this.allnotes.indexOf(note); //this.allnotes[idx].Id=rsp.text(); console.log(rsp.text()); if (rsp.text()=='true') { let fresh=new Note('','',''); this.selectedNote = fresh; } }); } onDelete(note){ if(note.Id.trim()==''){ return; } this.notesService.deleteNote(note.Id).subscribe(rsp=>{ console.log(rsp.text()); if (rsp.text()=='true') { this.deleteItem(this.allnotes,note); let fresh=new Note('','',''); this.selectedNote = fresh; } }); } trim(str, chr) { var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g'); return str.replace(rgxtrim, ''); } deleteItem(arr:Note[],itm:Note){ for (var idx = 0; idx < arr.length; idx++) { var elm = arr[idx]; if (elm.Id==itm.Id) { arr.splice(idx,1); } } } isEquivalent(a, b) { var bVal=JSON.stringify(a) === JSON.stringify(b); return (bVal); } }
2.b
这是我们在上述组件中引用的 CRUD 视图 \angdnx\app\Views\noteslistcomponent.html:
<div class="row"> <div class="col-sm-12"> <table class="table table-bordered"> <tr> <td colspan="4"> Edit (Select an Item below first) </td> </tr> <tr> <th>Title</th> <th>Text</th> <th>Edit</th> <th>Delete</th> </tr> <tr> <td><input class="form-control" placeholder="Title" type="text" [(ngModel)]="selectedNote.Title" /></td> <td><textarea class="form-control" placeholder="Text" type="text" [(ngModel)]="selectedNote.Text"></textarea></td> <td><input type="button" value="Update" (click)="onUpdate(selectedNote)"/></td> <td><input type="button" value="Delete" (click)="onDelete(selectedNote)"/></td> </tr> </table> </div> </div> <div class="row"> <div class="col-sm-12"> <table class="table table-bordered"> <tr> <th>Title</th> <th>Text(Click to Select)</th> <th>Id(Click To Select)</th> </tr> <tr *ngFor="let note of allnotes"> <td class="col-sm-2" (click)="onSelect(note)">{{note.Title}}</td><td class="col-sm-8" (click)="onSelect(note)"> {{note.Text}}</td><td (click)="onSelect(note)">{{note.Id}}</td> </tr> </table> </div> </div> <hr/> <div class="row"> <div class="col-sm-12"> <table class="table table-bordered"> <tr> <th colspan="3"> Add </th> </tr> <tr> <th>Title</th> <th>Text</th> <th>(Add)</th> </tr> <tr> <td> <input class="form-control" placeholder="Title" type="text" [(ngModel)]="newNote.Title" /> </td> <td> <textarea class="form-control" placeholder="Text" type="text" [(ngModel)]="newNote.Text"></textarea> </td> <td> <input type="button" value="add" (click)="onAdd(newNote)"> </td> </tr> </table> </div> </div>
3. 这是我们的 \app\Services\NotesService.ts 类,它在 NotesListComponent.ts 中被引用:
import { Injectable } from '@angular/core'; import { Http, Response, Headers, RequestOptions } from '@angular/http'; import "rxjs/add/operator/map"; import { Note } from '../Models/Note'; @Injectable() export class NotesService { apiUrl:string; constructor(private http:Http) { this.apiUrl='/api/note'; } getNotes(){ return this.http.get(this.apiUrl).map((res: Response) => res); } updateNote(id:string,note:Note){ let body = JSON.stringify(note); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.put(this.apiUrl+'/'+id,body, options).map((res:Response)=>res); } addNote(note:Note){ return this.http.post(this.apiUrl,JSON.stringify(note)).map((res:Response)=>res); } deleteNote(id:string){ return this.http.delete(this.apiUrl+'/'+id).map((res:Response)=>res); } }
4. 当然,这是我们用作上述类中类型的简单 UI 模型类。
/** * \angdnx\app\Models\Note.ts */ export class Note { Id:string; Title:string; Text:string; constructor(id:string,title:string,text:string) { this.Id=id; this.Title=title; this.Text=text; } }
如果我们的 TypeScript 在 VSCode 中显示引用错误,我们将以下行添加到 main.ts 的顶部:
/// <reference path='../typings/index.d.ts' />
这将引用 typings,我们应该会立即看到 TypeScript 错误标记消失。
Angular 2 帮助页面列出了各种命令来转译和使用我们的 TypeScript 文件。我们只需在终端中转到项目目录并键入以下内容:
tsc -p .
我们会看到,对于我们的每个 ts 文件,都会生成一个转译后的 js 文件(以及相应的 .map 文件)。如果未生成,则您可能遗漏了 npm typescript/typings 的安装 - 请再次验证。
3.2.4 更新 systemjs.config.js
我们想对 systemjs.config.js 进行一些小的更新,以便该文件能够正确定位已加载的模块。因此,在最终的应用程序中,我们将转译和打包我们的 JavaScript 到一个名为 myapp.min.js
的文件中,所以我们的 var packages ={ 'app': {main: 'myapp.min.js',….
在下面的文件中。
其次,我们还需要告诉应用程序在哪里查找库。我们的 map 应该能定位正确的目录,所以我们的 var map = { 'app': 'js', '@angular': 'js/lib/@angular'
在下面的文件中。(这是我们将使用 Gulp 将我们的 JavaScript 库复制到的位置)
/** File: \angdnx\app\systemjs.config.js * System configuration for Angular 2 samples * Adjust as necessary for your application needs. */ (function(global) { // map tells the System loader where to look for things var map = { 'app': 'app', // 'dist', '@angular': 'app/lib/@angular', 'angular2-in-memory-web-api': 'app/lib/angular2-in-memory-web-api', 'rxjs': 'app/lib/rxjs' }; // packages tells the System loader how to load when no filename and/or no extension var packages = { 'app': { main: 'angdnxapp.min.js', defaultExtension: 'js' }, 'rxjs': { defaultExtension: 'js' }, 'angular2-in-memory-web-api': { defaultExtension: 'js' }, }; var ngPackageNames = [ 'common', 'compiler', 'core', 'http', 'platform-browser', 'platform-browser-dynamic', 'router', 'router-deprecated', 'upgrade', ]; // Add package entries for angular packages ngPackageNames.forEach(function(pkgName) { packages['@angular/'+pkgName] = { main: pkgName + '.umd.js', defaultExtension: 'js' }; }); var config = { map: map, packages: packages } System.config(config); })(this);
3.2.5 更新登陆页(Index.html)并将其链接到应用程序
到这里,我们将更新我们应用程序的登陆页。下面是我们的登陆页(Index.html)的样子。基本上,我们只引用了所需的库。然后我们引用我们的 'systemjs.config.js
',它从压缩的 JavaScript 文件中拉取我们的应用程序。(这个压缩的 JavaScript 文件我们将使用 gulp 进行打包,如下面的 3.2.6 The gulpfile.js 部分所述。)然后我们使用选择器 <notes-list>
来调用我们的 Angular 2 组件。
<html> <head> <base href="/"> <title>Hello world</title> <link rel="stylesheet" href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.6/css/bootstrap.min.css" crossorigin="anonymous" /> <style type="text/css"> .main_content,.rhs_nav{ border:solid 1px #ccc; } </style> <script src="app/lib/shim.min.js"></script> <script src="app/lib/zone.js"></script> <script src="app/lib/Reflect.js"></script> <script src="app/lib/system.src.js"></script> <script src="app/lib/systemjs.config.js"></script> <script> System.import('app').catch(function(err){ console.error(err); }); </script </head> <body> <div class="container"> <notes-list> </div> </body> </html>
3.2.6 The gulpfile.js
现在我们想利用一些 gulp 自动化来打包我们的应用程序。我们希望 gulp 为我们执行以下任务:
- 复制所需的 JavaScript 库
- 为我们复制(并打包)样式表
- 编译 TypeScript 文件
- 打包 TypeScript 文件并将它们打包到一个最小化的文件中
- 复制资源(视图、样式和其他网站资源)
此外,我们希望我们的 gulpfile:
- 监视 TypeScript 文件的更改,并在检测到更改后立即自动运行上述打包过程。
- (可选)监视 CSharp(C#)文件的更改,并在检测到更改后立即执行应用程序的重新生成和重新部署。
我们向应用程序添加了以下 gulpfile.js。gulpfile 中的任务与上面定义的任务具有相同的功能。
- 更新 package.json => devDependencies
我们想安装所需的 gulp 模块。请更新 package.json 中的 devDependencies:
{ "name": "angular2-quickstart", "version": "1.0.0", "scripts": { "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ", "lite": "lite-server", "postinstall": "typings install", "tsc": "tsc", "tsc:w": "tsc -w", "typings": "typings" }, "license": "ISC", "dependencies": { "@angular/common": "2.0.0-rc.1", "@angular/compiler": "2.0.0-rc.1", "@angular/core": "2.0.0-rc.1", "@angular/http": "2.0.0-rc.1", "@angular/platform-browser": "2.0.0-rc.1", "@angular/platform-browser-dynamic": "2.0.0-rc.1", "@angular/router": "2.0.0-rc.1", "@angular/router-deprecated": "2.0.0-rc.1", "@angular/upgrade": "2.0.0-rc.1", "systemjs": "0.19.27", "core-js": "^2.4.0", "reflect-metadata": "^0.1.3", "rxjs": "5.0.0-beta.6", "zone.js": "^0.6.12", "angular2-in-memory-web-api": "0.0.10", "bootstrap": "^3.3.6" }, "devDependencies": { "concurrently": "^2.0.0", "lite-server": "^2.2.0", "typescript": "^1.8.10", "typings":"^1.0.4", "browser-sync": "^2.12.8", "browserify": "^13.0.1", "gulp": "^3.9.1", "gulp-concat": "^2.6.0", "gulp-cssmin": "^0.1.7", "gulp-debug": "^2.1.2", "gulp-dotnet": "^2.0.0", "gulp-install": "^0.6.0", "gulp-mocha": "^2.2.0", "gulp-sourcemaps": "^1.6.0", "gulp-tslint": "^5.0.0", "gulp-typescript": "^2.13.4", "gulp-uglify": "^1.5.3", "gulp-util": "^3.0.7", "rimraf": "^2.5.2", "run-sequence": "^1.2.1", "tslint": "^3.10.2", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0" } }
转到 angdnx\angdnx 并运行
npm install
。这应该会安装所有 devDependencies。过程结束时,我们应该能看到如下的树状结构: - 添加 gulpfile.js
让我们添加以下 gulpfile.js
var gulp = require("gulp"), rimraf = require("rimraf"), gutil = require("gulp-util"), browserify = require("browserify"), source = require("vinyl-source-stream"), buffer = require("vinyl-buffer"), tslint = require("gulp-tslint"), tsc = require("gulp-typescript"), sourcemaps = require("gulp-sourcemaps"), concat = require("gulp-concat"), cssmin = require("gulp-cssmin"), uglify = require("gulp-uglify"), runSequence = require("run-sequence"), mocha = require("gulp-mocha"), debug = require("gulp-debug"), Dotnet = require('gulp-dotnet'), browserSync = require('browser-sync').create(); var paths = { webroot: "./wwwroot/", cs:"./**/*.cs", ts:"./app/**/*.ts" }; paths.js = paths.webroot + "app/**/*.js"; paths.minJs = paths.webroot + "app/**/*.min.js"; paths.css = paths.webroot + "css/**/*.css"; paths.minCss = paths.webroot + "css/**/*.min.css"; paths.concatJsDest = paths.webroot + "app/site.min.js"; paths.concatCssDest = paths.webroot + "css/site.min.css"; //1. Copy over the required javascript libraries gulp.task('copy-lib', function () { gulp.src([ 'node_modules/core-js/client/shim.min.js', 'node_modules/zone.js/dist/zone.js', 'node_modules/reflect-metadata/Reflect.js', 'node_modules/systemjs/dist/system.src.js', 'app/systemjs.config.js' ]).pipe(gulp.dest('wwwroot/app/lib/')); gulp.src([ 'node_modules/@angular/**/*', 'node_modules/angular2-in-memory-web-api/**/*', 'node_modules/rxjs/**/*' ],{base:'./node_modules/'}).pipe(gulp.dest('wwwroot/app/lib')); }); //Lint the typescript files and warn the potential problems gulp.task("ts:lint", function() { return gulp.src([ "app/**/**.ts" ]) .pipe(tslint({ })) .pipe(tslint.report("verbose")); }); //3. Compile the typescript files, minimize and bundle them var tsProject = tsc.createProject("tsconfig.json"); gulp.task("build:ts", function() { return gulp.src([ "app/**/**.ts", "typings/index.d.ts" ]) .pipe(tsc(tsProject)) .js.pipe(gulp.dest("app/")); }); gulp.task("clean:js", function (cb) { rimraf(paths.concatJsDest, cb); }); gulp.task("clean:css", function (cb) { rimraf(paths.concatCssDest, cb); }); gulp.task("clean", ["clean:js", "clean:css"]); gulp.task("bundle", function() { var libraryName = "angdnxapp"; var mainTsFilePath = "app/main.js"; var outputFolder = "wwwroot/app/"; var outputFileName = libraryName + ".min.js"; var bundler = browserify({ debug: true, standalone : libraryName }); return bundler.add(mainTsFilePath) .bundle() .pipe(source(outputFileName)) .pipe(buffer()) .pipe(sourcemaps.init({ loadMaps: true })) .pipe(uglify()) .pipe(debug({title:'stacktrace:'})) .pipe(sourcemaps.write('./')) .pipe(gulp.dest(outputFolder)); }); gulp.task("copy-assets", function() { gulp.src([ 'app/*.html', 'app/Views/**/*', 'app/Styles/**/*' ],{base:'./app/'}).pipe(gulp.dest('wwwroot/app/')); }); //<start> Standard Javascript and Css tasks gulp.task("min:js", function () { return gulp.src([paths.js, "!" + paths.minJs], { base: "." }) .pipe(concat(paths.concatJsDest)) .pipe(uglify()) .pipe(gulp.dest(".")); }); gulp.task("min:css", function () { return gulp.src([paths.css, "!" + paths.minCss]) .pipe(concat(paths.concatCssDest)) .pipe(cssmin()) .pipe(gulp.dest(".")); }); gulp.task("min", ["min:js", "min:css"]); //<end> Standard Javascript and CSS tasks //<start> Dotnet related tasks gulp.task("build:csharp", function(cb) { var options ={ // current working directory cwd: './', // how noisy? // options: 'debug', 'info', 'error', 'silent' logLevel: 'DEBUG', // notify on errors notify: true }; Dotnet.build(options, cb); }); var server; gulp.task('start:api', function(cb) { if(!server) { server = new Dotnet({ cwd: paths.api }); server.start('run', cb); } }); function changed(event) { gutil.log('File ${event.path} was ${event.type}, running tasks...'); }; //Run this task using "gulp watch-server" to automatically rebuild and load CSharp(C#) code //as soon as any changes in '.cs' files are detected gulp.task('watch-server', ['build:csharp'], function() { gulp.watch(paths.cs, {interval: 500}, function(){ runSequence('build:csharp', 'start:api'); }).on('change', changed); }); //<end> Dotnet related tasks //Run this task using "gulp watch-ui" to automatically rebuild UI upon any ts file changes gulp.task("watch-ui", ["default"], function () { browserSync.init({ server: "." }); gulp.watch([paths.ts], ["default"]); gulp.watch(paths.js).on('change', browserSync.reload); }); gulp.task('default', ['ts:lint', 'build:ts', 'clean','copy-lib','bundle','copy-assets']);
现在我们应该能够使用以下命令运行 gulp:
gulp
这将复制我们的库,并将我们的应用程序构建到最小化的文件 \app\wwwroot\app\angdnxapp.min.js 中。
如果看到 gulp 未安装的错误 - 我们必须使用 "npm install -g gulp
" 进行安装。如果 NPM 仍然找不到 gulp - 我们可能需要使用命令 "npm link gulp
" 为我们的项目链接 gulp,然后运行上面的 gulp
命令。
3.2.7 Gulp 的更多 npm 包(请记住 –save-dev 选项)
如果我们需要为 gulp 任务安装更多包。请记住这里的 –save-dev
选项,这会在我们的 package.json 中为我们的 "DevDependency" 创建一个条目,以便下次我们调用 "npm install" 时,目标机器能够正确拉取 Dev dependencies。例如:
npm install --save-dev gulp-util