AngularJS 应用程序中 TypeScript 入门






4.54/5 (16投票s)
本文将帮助您开始在 AngularJS 应用程序中使用 TypeScript。
引言
AngularJS 是所有前端 Web 开发框架中当之无愧的冠军,好吧,我不是专家,因为老实说我并没有过多地使用过 Knockout、Backbone、AmberJS 等其他流行库,但最近几个月我一直在探索 AngularJS,并且我非常喜欢它。
AngularJS 带来了许多优点,它是一个设计精良、强大且经过深思熟虑的框架。在我看来,如果您是一名 Web 开发者,那么投入时间学习 AngularJS 是完全值得的,即使您不怎么从事前端工作,也不打算在不久的将来在您的项目中使用它。
背景
随着 ng-conf 上关于 AngularJS 和 TypeScript 协作的公告以及“Angular 2:基于 TypeScript 构建”的声明,开发者社区从 JavaScript 向 TypeScript 迁移以用于 AngularJS 项目的势头似乎非常强劲。我不是 TypeScript 的倡导者,也是 JavaScript 的忠实粉丝,但在这篇文章中,我将向您展示如何使用 TypeScript 开始使用 AngularJS,而避免对 TypeScript 和 JavaScript 进行任何比较。
本文结束时我们将实现什么?
我们将使用 TypeScript 构建一个非常简单的 AngularJS 应用程序。该应用程序将有一个视图,其中包含一个名为“获取我喜欢的曲目”的按钮。点击按钮后,它将显示一个曲目列表,很简单,不是吗?
我将使用 Visual Studio 2013 来创建演示应用程序,并且为了保持简洁,不会使用任何 REST API。
注意:虽然附带的解决方案是在 VS 2013 中创建的,但实际上并不需要。您可以下载附带的代码,然后将代码(当然要排除 .sln 和 .proj 文件)复制到解决方案中,它应该可以在任何 IDE 中正常工作,无需任何修改。
废话不多说,让我们开始吧。
创建项目结构
打开 Visual Studio,创建一个空的 Web 项目。删除 web.config 文件,我们不需要它。使用 nuget 包管理器添加以下引用,您也可以直接添加它们,nuget 是可选的。
- AngularJS.Core
- AngularJS.Route
- angularjs.TypeScript.DefinitelyTyped(这是 AngularJS 的 TypeScript 定义)
可选地,您可以添加 Bootstrap 来美化您的应用程序。
在项目根目录中创建一个名为“app”的文件夹,我们将在其中添加应用程序的代码。
创建接口
在我们的应用程序中,我们将使用一个 AngularJS 服务,该服务将与后端 REST API 通信以获取曲目列表。我喜欢将所有接口定义与主要业务逻辑分开,所以让我们在“app”文件夹内创建另一个名为“interfaces”的文件夹,在该文件夹内创建一个空的 TypeScript 文件,并在其中添加以下代码。
module angularWithTS.Interfaces {
export interface IPlaylistService {
getPlayList: () => Array<ITrack>;
}
export interface ITrack {
title: string;
artist: string;
rating: number;
}
}
这里我们创建了一个名为“angularWithTS.Interfaces”的 TypeScript 模块。请注意,TypeScript 模块更像是其他编程语言中的命名空间,与 AngularJS 模块无关。但是,您稍后会看到我将 AngularJS 模块命名为“angularWithTS”,这只是我个人命名事物的一种风格,但您可以肯定地将其命名为任何您喜欢的名字。
您可以看到我在这个文件中添加了两个接口定义。接口“IPlaylistService”有一个名为“getPlayList”的方法,方法签名表明它不接受任何参数,并返回一个 ITrack 数组。
ITrack 只是同一文件中定义的另一个接口,它定义了我们的曲目对象的契约。使用接口可以确保我们应用程序中不会传递随机对象,并可以避免代码中意外的错误。这是 TypeScript 最突出的功能之一。
实现接口
您已经定义了服务接口“IPlaylistService”,现在让我们来实现它。在“app”文件夹内创建另一个文件夹并命名为“services”。然后添加一个名为“playlistService.ts”的新 TypeScript 文件,并在该文件中添加以下代码。
/// <reference path="../interfaces/interfaces.ts" />
module angularWithTS.Services {
export class PlayListService implements angularWithTS.Interfaces.IPlaylistService {
httpService: ng.IHttpService
static $inject = ["$http"];
constructor($http: ng.IHttpService) {
this.httpService = $http;
}
getPlayList = () => {
//For the purpose of this demo I am returning the hard coded values, hoever in real world application
//You would probably use "this.httpService.get" method to call backend REST apis to fetch the data from server.
var res: Array<angularWithTS.Interfaces.ITrack> = [
{ title: "Numb", artist: "Linkin Park", rating: 5 },
{ title: "Fire Flies", artist: "Owl City", rating: 4.3 },
{ title: "Yellow", artist: "coldplay", rating: 4.5 },
{ title: "Skyfall", artist: "Adele", rating: 4.5 }
];
return res;
}
}
angular.module("angularWithTS").service("angularWithTS.Services.PlayListService", PlayListService);
}
如上代码所示,我们将服务封装在另一个名为“angularWithTS.Services”的命名空间中。“export”关键字确保我们的服务可以从“angularWithTS.Services”模块调用,而不仅仅是本地模块。
为了创建服务,我们创建一个新类,该类实现了“IPlaylistService”接口。在 AngularJS 应用程序中有 5 种创建服务的方法:service、factory、provider、value 和 constant,它们都有各自的用例和限制。AngularJS 将它们称为创建服务的 *recipe*,而 service 是我为本次演示选择的 recipe 名称。
我知道 service recipe 来创建服务真的令人困惑,但由于 AngularJS 团队在命名事物时没有咨询我们,我们将尝试与之共存。您可以在 AngularJS 开发人员指南中找到有关服务的更多信息。
我们的服务需要与后端 API 通信,因此需要 AngularJS 服务“$http”。我们可以在服务构造函数中指定此依赖项,Angular 将在运行时满足它。
请注意,函数的参数名为“$http”,其类型指定为“ng.IHttpService”。但是,为了让您的服务类能够通过像 Grunt 和 Gulp 这样的流行前端工具的最小化和压缩过程,您需要进行一个额外的步骤,如下所示。
static $inject = ["$http"];
$inject 是 AngularJS 框架将使用的特殊属性。请确保它被标记为“static”。它的值是一个字符串数组,每个字符串都是一个特定服务的标识符。此外,字符串在数组中的添加顺序很重要,此顺序应与构造函数参数的顺序相同。AngularJS 将在运行时检查此属性以确定需要注入哪些服务。
您还可以看到我们的服务确实实现了“IPlaylistService”接口中的“getPlayList”函数。但是,我们实际上并没有调用任何 REST API,而是返回硬编码的值,但我相信您已经明白了。
angular.module("angularWithTS").service("angularWithTS.Services.PlayListService", PlayListService);
上一行是标准的 AngularJS 代码,它将我们的类添加为 AngularJS 模块中的一个服务。我们还没有为我们的应用程序创建 Angular 模块,所以让我们在下一步创建一个。
创建 AngularJS 模块
要创建模块,请在“app”文件夹中添加“app.module.ts”文件。我喜欢这样命名模块文件,但同样,这只是个人偏好。在“app.module.ts”文件中添加以下代码:
((): void=> {
var app = angular.module("angularWithTS", ['ngRoute']);
app.config(angularWithTS.Routes.configureRoutes);
})()
我们添加了 IIFE 并使用了 TypeScript lambda 函数来定义我们的代码。其中,第一行是创建了一个名为“angularWithTS”的 AngularJS 模块,它依赖于“ngRoute”模块。下一行是通过提供一个在“angularWithTS.Routes”类中定义的函数引用来配置我们的应用程序的路由信息,我们还没有创建该类,所以让我们快速创建一个。
定义路由
在“app”文件夹中再添加一个名为“app.routes.ts”的文件,并在其中添加以下代码:
/// <reference path="../scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../scripts/typings/angularjs/angular-route.d.ts" />
module angularWithTS {
export class Routes {
static $inject = ["$routeProvider"];
static configureRoutes($routeProvider: ng.route.IRouteProvider) {
$routeProvider.when("/home", { controller: "angularWithTS.controllers.tsDemoController", templateUrl: "/app/views/playlist.html", controllerAs: "playList" });
$routeProvider.otherwise({ redirectTo: "/home" });
}
}
}
上面的代码非常直接。但是,我想强调一点,即第一个注册路由的“controllerAs”参数。为“playList”提供的值表示,将绑定到该路由的控制器将通过“playList”别名在该路由的视图中访问。
这一点很重要,因为您可以借助此别名访问控制器的属性/方法,而无需依赖该控制器的“$scope”。我们将“angularWithTS.controllers.tsDemoController”控制器映射到我们的路由,我们将在下一步创建它。
创建控制器
让我们通过在“app”文件夹内添加一个名为“controllers”的新文件夹来创建控制器,然后添加一个名为“tsDemoController.ts”的新 TypeScript 文件,并添加以下代码。
/// <reference path="../services/playlistservice.ts" />
/// <reference path="../interfaces/interfaces.ts" />
/// <reference path="../../scripts/typings/angularjs/angular.d.ts" />
module angularWithTS.controllers {
export class TSDemoController {
playListService: angularWithTS.Interfaces.IPlaylistService;
static $inject = ["angularWithTS.Services.PlayListService"];
constructor(playListService: angularWithTS.Interfaces.IPlaylistService) {
this.playListService = playListService;
}
favorites: Array<angularWithTS.Interfaces.ITrack>;
getFavourites = () => {
this.favorites = this.playListService.getPlayList();
}
}
angular.module("angularWithTS").controller("angularWithTS.controllers.tsDemoController", TSDemoController);
}
我们为控制器创建了一个名为“TSDemoController”的新类,并在构造函数中指定了它对“angularWithTS.Services.PlayListService”服务的依赖。再次注意“$inject”属性。
我们的控制器有一个名为“favorites”的属性,其类型为“Array<angularWithTS.Interfaces.ITrack>”。我们将使用此属性来绑定我们的视图。它还有一个名为“getFavourites”的函数,该函数只需使用服务调用的结果填充“favorites”属性。我们将在视图中将此函数用作点击事件处理程序。接下来,我们将创建视图。
创建视图
要将视图添加到我们的项目中,请在“app”文件夹中再添加一个名为“views”的文件夹,然后添加一个空的 HTML 文件“playlist.html”。在 HTML 文件中添加以下代码:
<div class="jumbotron">
<ul class="list-group">
<li ng-show="playList.favorites" class="list-group-item" ng-repeat="t in playList.favorites">
{{t.title}}
</li>
</ul>
<button class="btn btn-block" ng-click="playList.getFavourites()">Get Favorites Tracks</button>
</div>
如您所见,我们通过别名“playList.favorites”访问控制器属性“favorites”,而不是依赖“$scope”。其余部分不言自明。
整合
最后,我们几乎完成了应用程序。让我们在“app”中添加一个外壳“Index.html”文件,如下所示。请确保您已将所有第三方脚本和 CSS 文件添加到项目中,这些文件在 Index.html 中有引用。
<!DOCTYPE html>
<html ng-app="angularWithTS">
<head>
<title>AngularJS With TypeScript</title>
<link href="content/css/bootstrap.min.css" rel="stylesheet" />
<link href="content/css/bootstrap.custom.min.css" rel="stylesheet" />
</head>
<body>
<div class="container">
<div class="page-header">Click the following button to get your favorite tracks.</div>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div ng-view="">
</div>
</div>
</div>
</div>
<script src="scripts/angular.js"></script>
<script src="scripts/angular-route.js"></script>
<script src="app/app.routes.js"></script>
<script src="app/app.module.js"></script>
<script src="app/services/playlistService.js"></script>
<script src="app/controllers/tsDemoController.js"></script>
</body>
</html>
结论
在这篇文章中,我只是触及了皮毛。当然,一篇文章无法充分展示 AngularJS 或 TypeScript。希望它能帮助您开始在 AngularJS 项目中使用 TypeScript。