AngularJS是Google开源出来的一款 Javascript MVC 框架。利用AngularJS,你可以构建结构清晰、便于测试和维护的前端应用。
使用AngularJS,你可以通过directive去定义很多自己的HTML元素属性。AngularJS无缝衔接了HTML(view)和Javascript(model),这样你就不需要去过多地关注Dom如何变化,你只需专注的处理你的数据。
AngularJS和服务器通信非常方便。和大多数Javascript的MVC框架一样, 只要你的应用提供一个RESTful API,AngularJS就可以和你的服务器相配合。同时,AngularJS提供了基于XHR的服务,这将大大简化你的代码,也方便将可复用的服务抽象成API调用。
Raoni Boaventura提供了一个教程,一步一步地教你写一个简单的AngularJS应用,让我们一起来看一下吧。
为了简化问题,我们要做的是一个直接从网络上拉取信息的应用,比如,一个查看方程式赛车比赛信息的应用。直接用
Ergast的api获取信息。
可以看一下这个
demo ,对我们要做一个什么样的应用有个大致的概念。开动吧
推荐使用
augular-seed,提供了一个很好的项目骨架。
我们的应用的骨架大概是这样的:
现在要开始写代码了。从最重要的页面开始吧:锦标赛的表格。
HTML大概是这个样子的(为了提高可读性,先忽略CSS):
<body ng-app="F1FeederApp" ng-controller="driversController">
<table>
<thead>
<tr><th colspan="4">Drivers Championship Standings</th></tr>
</thead>
<tbody>
<tr ng-repeat="driver in driversList">
<td>{{$index + 1}}</td>
<td>
<img src="img/flags/{{driver.Driver.nationality}}.png" />
{{driver.Driver.givenName}} {{driver.Driver.familyName}}
</td>
<td>{{driver.Constructors[0].name}}</td>
<td>{{driver.points}}</td>
</tr>
</tbody>
</table>
</body>
我想你会注意到模板里面有一些
{{
和
}}
之类的表达式。我们可以在里面做一些计算。举一些例子吧:
{{ 1 + 1 }}
{{ 946757880 | date }}
{{ user.name }}
是不是和javascript很像?不过,虽然它们很强大,我们不应该用它们去实现一些高层级的逻辑——这该交给directive。
理解基本的directive
上面的模板里还有一些类似
ng-attributes
的语句,这些正是directive。
directive让AngularJS把特定的行为附加到DOM元素中。让我们看一下上面的模板中的例子:
-
初始化你的应用,定义其作用域。在 AngularJS 中,同一页面可以有多个应用,ng-app
指令表明应用的首尾位置。ng-app
-
定义视图由哪个控制器负责。在我们的例子中,ng-controller
提供了车手的列表(driversController
)。driversList
-
这个最常用。当使用循环的时候,ng-repeat
定义模板的范围。在我们的例子中,就ng-repeat
中的每个车手,driversList
会生成重复的行。ng-repeat
添加控制器
当然,没有控制器,我们的视图什么也干不了。让我们在
controllers.js
中添加一个
driversController
:
angular.module('F1FeederApp.controllers', []).
controller('driversController', function($scope) {
$scope.driversList = [
{
Driver: {
givenName: 'Sebastian',
familyName: 'Vettel'
},
points: 322,
nationality: "German",
Constructors: [
{name: "Red Bull"}
]
},
givenName: 'Fernando',
familyName: 'Alonso'
points: 207,
nationality: "Spanish",
{name: "Ferrari"}
}
];
});
你可能注意到了我们将
$scope
传递给了控制器。
$scope
变量将控制器和视图相连接。事实上,它储存了模板中会用到的所有数据。任何你加入的内容(比如我们的例子中的
driversList
)可以直接在视图中访问。现在我们先用一个静态的数据数组,稍后我们会把它换成API服务。
在
app.js
中加入:
angular.module('F1FeederApp', [
'F1FeederApp.controllers'
]);
这行代码让我们初始化了我们的应用,同时也登记了需要的依赖。稍后我们会回到这个文件。
好了,现在让我们把这一切在
index.html
中整合起来:
<!DOCTYPE html>
<html>
<head>
<title>F-1 Feeder</title>
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-route.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<script type="text/javascript" src="js/controllers.js"></script>
<script type="text/javascript" src="js/services.js"></script>
</head>
</html>
现在你可以尝试运行下这个应用了。
如果你希望调试应用,建议看下Chrome的
Batarang插件。
从服务器获取信息
既然我们已经知道如何展示这些数据了,现在该是我们从RESTful服务器获取信息的时候了。
AngularJS提供的
$http
$resource
帮助我们和服务器通讯。
$http
是以
XMLHttpRequest和[JSONP]为基础的抽象层,
$resource
则提供更高层的抽象。在这里我们使用
$http
。
为了将API调用从控制器中抽象出来,我们创建一个自己定制的服务,该服务将抓取我们需要的信息,将
$http
封装起来。在
services.js
angular.module('F1FeederApp.services', []).
factory('ergastAPIservice', function($http) {
var ergastAPI = {};
ergastAPI.getDrivers = function() {
return $http({
method: 'JSONP',
url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK'
});
}
return ergastAPI;
});
开头两行,我们创建了一个名为
F1FeederApp.services
的新模块,并在
ergastAPIservice
模块内注册了服务。注意,我们将
$http
传递给了该服务。这就告诉了Angular的
依赖注入引擎我们的新服务依赖于
$http
服务。
类似地,我们需要让Angular将我们的新模块包含到应用中。在
app.js
注册下即可:
'F1FeederApp.controllers',
'F1FeederApp.services'
现在我们只需调整一下
controller.js
,将
ergastAPIservice
作为依赖:
controller('driversController', function($scope, ergastAPIservice) {
$scope.nameFilter = null;
$scope.driversList = [];
ergastAPIservice.getDrivers().success(function (response) {
//Dig into the responde to get the relevant data
$scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
});
好了,重新启动一下应用,看看结果如何。注意我们完全没有改动模板,只是增加了一个
nameFilter
。让我们把这个变量用起来。
过滤器
好极了!我们的控制器已经可以工作了。但是它只能显示一个车手的列表。让我们加一些功能吧。我们来实现一个简单的文本搜索框,可以过滤列表。将以下这行加入到
index.html
,加在
<body>
标签下面:
<input type="text" ng-model="nameFilter" placeholder="Search..."/>
现在我们要用上
ng-model
指令了。这个指令将文本框绑定到
$scope.nameFilter
变量,并且确保该变量的值会及时更新。现在让我们稍微调整下
index.html
,加上一行
ng-repeat
指令。
<tr ng-repeat="driver in driversList | filter: nameFilter">
这一行告诉
ng-repeat
,在输出数据之前,车手数组先要经过
nameFilter
的过滤。
此刻就是双向数据绑定发挥威力的时候了:每次你在搜索框里键入一些值的时候,Angular会及时更新
$scope.nameFilter
的内容。由于绑定是双向的,所以
nameFilter
更新的时候,相应的指令(
ng-repeat
)也会获得新的数值,然后视图会立刻更新。
重启你的应用,看看搜索框。
注意,过滤器会搜索所有属性中的关键词,包括你不想包括的内容。假设你只想通过
Driver.givenName
Driver.familyName
过滤:首先,在
driversController
文件的
$scope.driversList = [];
下加入这样一行:
$scope.searchFilter = function (driver) {
var keyword = new RegExp($scope.filterName, 'i');
return !$scope.filterName || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName);
};
现在回到
index.html
,更新包括
ng-repeat
的那行:
<tr ng-repeat="driver in driversList | filter: searchFilter">
重新启动应用,现在你可以通过姓名搜索了。
路由
接下来我们要创建一个车手详情页面,当我们点击车手的时候,我们就可以看到关于他的一些详细信息。
首先,我们在
app.js
里加入
$routeProvider
服务,这个服务将帮助我们处理应用路由。然后我们加入两个路由:一个转向锦标赛表格,另一个转向车手详情。
'F1FeederApp.services',
'ngRoute'
]).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}).
when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}).
otherwise({redirectTo: '/drivers'});
}]);
修改之后,访问
http://domain/#/drivers
,这将加载
driversController
,然后在
partials/drivers.html
寻找需要渲染的部分视图。等等!我们好像还没有部分视图?我们需要创建他们。
部分视图
AngularJS允许你将路由绑定到特定的控制器和视图。不过我们首先需要告诉Angular在哪里渲染这些部分视图。这需要使用
ng-view
指令。修改一下你的
index.html
<body ng-app="F1FeederApp">
<ng-view></ng-view>
现在,只要是通过应用路由浏览,Angular 就会加载相应的视图,并且
<ng-view>
标签处渲染。你所需要做的只是创建一个名为
partials/drivers.html
的文件,然后将锦标赛表格放在那里。同时我们也将将车手的姓名和详情页面连接起来。
<table>
<thead>
<tr><th colspan="4">Drivers Championship Standings</th></tr>
</thead>
<tbody>
<tr ng-repeat="driver in driversList | filter: searchFilter">
<td>{{$index + 1}}</td>
<td>
<img src="img/flags/{{driver.Driver.nationality}}.png" />
<a href="#/drivers/{{driver.Driver.driverId}}">
{{driver.Driver.givenName}} {{driver.Driver.familyName}}
</a>
</td>
<td>{{driver.Constructors[0].name}}</td>
<td>{{driver.points}}</td>
</tr>
</tbody>
</table>
最后,让我们确定下详情页面要展示什么。一个总结了车手相关信息(比如生日、国籍)的页面,同时包括一个最近成绩的表格。在
services.js
里加入这些:
angular.module('F1FeederApp.services', [])
.factory('ergastAPIservice', function($http) {
ergastAPI.getDriverDetails = function(id) {
url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK'
ergastAPI.getDriverRaces = function(id) {
url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK'
这次我们把车手的ID提供给服务,这样我们就可以获取特定车手的信息了。修改一下
controllers.js
/* Drivers controller */
$scope.searchFilter = function (driver) {
var re = new RegExp($scope.filterName, 'i');
return !$scope.filterName || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName);
};
//Digging into the response to get the relevant data
}).
/* Driver controller */
controller('driverController', function($scope, $routeParams, ergastAPIservice) {
$scope.id = $routeParams.id;
$scope.races = [];
$scope.driver = null;
ergastAPIservice.getDriverDetails($scope.id).success(function (response) {
$scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0];
ergastAPIservice.getDriverRaces($scope.id).success(function (response) {
$scope.races = response.MRData.RaceTable.Races;
});
值得注意的是我们将
$routeParams
服务插入了车手控制器。这个服务允许我们使用
$routeParams.id
访问URL参数(比如
:id
现在我们已经有数据了,我们只需要处理一下局部视图了。创建一个
partials/driver.html
:
<section id="main">
<nav id="secondary" class="main-nav">
<div class="driver-picture">
<div class="avatar">
<img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" />
<img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/>
{{driver.Driver.givenName}}<br/>{{driver.Driver.familyName}}
</div>
</div>
<div class="driver-status">
Country: {{driver.Driver.nationality}} <br/>
Team: {{driver.Constructors[0].name}}<br/>
Birth: {{driver.Driver.dateOfBirth}}<br/>
<a href="{{driver.Driver.url}}" target="_blank">Biography</a>
</nav>
<div class="main-content">
<table class="result-table">
<thead>
<tr><th colspan="5">Formula 1 2013 Results</th></tr>
</thead>
<tbody>
<tr>
<td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td>
</tr>
<tr ng-repeat="race in races">
<td>{{race.round}}</td>
<td><img src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td>
<td>{{race.Results[0].Constructor.name}}</td>
<td>{{race.Results[0].grid}}</td>
<td>{{race.Results[0].position}}</td>
</tbody>
</table>
</div>
</section>
注意这次我们用上了
ng-show
。只有当你提供的表达式是
true
的时候,它才会显示HTML元素。在我们的例子中,只有当控制器加载了车手对象后才会显示头像。
完成
加上一些CSS之后,大致是这样的效果:
现在你的应用已经可以上线了。确认下路由能工作。你可以在
index.html
加入一个静态的菜单以加强导航。一切皆有可能。
结论
我们已经介绍了开发一个简单应用所需的一切。最后别忘了,Angular是一个非常强大的框架。我们只是试了试水而已。以后有机会将向大家展示Angular区别于其他前端MVC框架的特性:可测试性。我们将评测使用
Karma编写和运行测试的过程,介绍持续集成的工具
Yeomen、
Grunt Bower,以及Angular的其他优势。敬请期待。