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的其他優勢。敬請期待。