把玩了AngularJS一個月,試作了CRUD這些基本的功能,其實也不難,只是得先搞懂service、directive、factory、scope...這些物件的原理、用法。
我先以bootstrap拉了一個主版面出來,如下圖所示:
View的區塊就是讓template透過Route指引Multi Views來自動變化。而我想在一開始時先表列資料,點選某筆資料後,畫面切換到資料的明細,亦即List-Detail的呈現方式。
List畫面 :
Detail畫面 :
在檔案架構上,我以功能為導向,命名了一個Phone的資料夾,建立3個js檔:services.js、directives.js、controllers.js。
萬一哪邊有問題,能很快速的找到該檔案中的哪一支程式需要檢查。
而在版面上,我建構了一支主版(Phone.aspx),以及兩支樣版(PhoneList.html、PhoneDetail.html)。在主板上,最重要的是要加入一個<div ng-view></div>的區塊,讓樣板能從該區塊被載入,例如:
<%@Page Language="C#"AutoEventWireup="true"CodeFile="Phone.aspx.cs"Inherits="Phone"%>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" ng-app="Phone" >
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<form id="form1"runat="server">
<divclass="row-fluid text-center banner">
<h1>Master Page - Header</h1>
</div>
<div ng-view></div>
<div class="navbar footer">
<div class="row-fluid text-center"><h1>Master Page - Footer</h1></div>
</div>
</div>
</form>
</body>
</html>
而在樣板檔案中,你則可以恣意的以<div></div>區塊搭配Bootstrap的css類別宣告來設計版面。例如PhoneList.html:
<style type="text/css">
.hover{background-color:lightgray}
</style>
<div ng-controller="PhoneListCtrl">
<div class="row-fluid">
<button type="button" class="btn btn-primary pull-left" ng-click="AddPhone()">Add List</button>
</div>
<div class="row-fluid" ng-repeat="phone in phones" ng-mouseover="RowHightlight($index)" ng-class="{hover : selectedRow==$index}">
<div class="span2"style="margin-top:15px; padding:5px;"><img ng-src="{{phone.imageUrl}}" /></div>
<div class="span8 offset1" >
<div class="row success">
<i class="icon-star-empty"></i>
<b><a href="#/detail/{{phone.id}}">{{phone.name}}</a></b>
</div><br/>
<div class="row" style="text-align:left">
<span> {{phone.snippet}}</span>
<button class="btn btn-primary pull-right" ng-click="DeletePhone(phone)">Delete</button>
</div>
<hr/>
</div>
</div>
</div>
js的部分,首先看services.js
var appMoudle = angular.module('Phone', ['ngResource']);
appMoudle.factory('ServicePhone', ['$resource', function ($resource) {
return $resource('/api/Phone/:id', { id: '@id' }, {
query: { method: 'GET', isArray: true }, //取得phones列表(Read)
get: {method:'GET'}, //取得phone物件(Read)
save: { method: 'POST'}, //新增(Create)
update: {method:'PUT'}, //更新(Update)
remove: {method:'DELETE'} //刪除(Delete)
});
}]);
由於我有調用ngResource這個Service來幫我處理資料往返前後端,所以必須把它加入陣列中。記得將angular-resource.js參考拉到主版檔案中。然後利用了工廠方法定義了名為ServicePhone服務,並且在此物件中定義了四個方法:save、query、get、update、remove來對應C、R、U、D。爾後這個服務將用來注入controller中。
接下來controllers.js:
appMoudle.config(['$routeProvider', function ($routeProvider) { //定義路由表
$routeProvider.
when('/', {
templateUrl: '/View/PhoneList.html',
controller: 'PhoneListCtrl'
}).
when('/detail/:id', {
templateUrl: '/View/PhoneDetail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({ redirectTo: '/' });
}]);
首先透過註冊一個底層$routeProvider,來配置路由。如果Path為/結尾,則ng-View中則導入PhoneList.html樣板,並且為該樣版配置PhoneListCtrl Controller;要是Path包含/detail/id編號,則導入PhoneDetail.html樣板,並為該樣版配置PhoneDetailCtrl Controller。
以下則定義了兩個Controller:PhoneListCtrl、PhoneDetailCtrl,並且為Controller注入先前自行定義的ServicePhone服務,而利用該服務調用\修改\刪除資料時,語法為
ServicePhone.query()
ServicePhone.get({id:$route.current.params.id}):
ServicePhone.save({}, phone)
ServicePhone.update({}, phone)
是不是很簡短易懂^^
appMoudle.controller({
'PhoneListCtrl': ['$scope', 'ServicePhone', function ($scope, ServicePhone) {
$scope.phones = ServicePhone.query();
$scope.RowHightlight = function (row) { //滑鼠移過時,背景色hightlight
$scope.selectedRow = row;
}
$scope.DeletePhone = function (phone) { //刪除某筆資料
var index = jQuery.inArray(phone, $scope.phones);
//勿用ngResource的delete,delete在IE下是關鍵字,使用會有問題,改用remove
ServicePhone.remove({ id: index });
$scope.phones.splice(index, 1); //刪除phones中的某一筆資料
}
$scope.AddPhone = function () { //新增一筆資料
var phone = {};
phone.age = "0";
phone.id = $scope.phones.length + 1;
phone.imageUrl = "img/phones/motorola-xoom-with-wi-fi.0.jpg";
phone.name = "Motorola XOOM\u2122 with Wi-Fi";
phone.snippet = "The Next; Next Generation\r\n\r\nExperience the future with Motorola XOOM \
with Wi-Fi; the world's first tablet powered by Android 3.0 (Honeycomb).";
phone.price = 12000
ServicePhone.save({}, phone, function (phone) { //success callback
$scope.phones.push(phone);
alert('Add Phone Success!');
})
}
}],
'PhoneDetailCtrl': ['$scope', 'ServicePhone','$route', function ($scope, ServicePhone,$route) {
var phone = ServicePhone.get({id:$route.current.params.id}); //取得id後,傳回後端調用資料
$scope.phone = phone;
$scope.isShow = true;
$scope.EditPhone = function (phone) { //更新資料
ServicePhone.update({}, phone, function (phone) {
alert('Edit Phone Success!');
});
}
}]
});
另外我在PhoneList.html樣板上,加了滑鼠移到列表時highlight的功能,先在樣板上定義ng-mouseover directive,例如:ng-mouseover="RowHightlight($index)",RowHightlight是定義在PhoneDetailController中的方法,$index則是ng-repeat directive內建的屬性,回傳值為列表的index值。另外還提供了其他的屬性($first、$middle、$last....),請參閱官網http://docs.angularjs.org/api/ng.directive:ngRepeat。簡單來說,我將列表的index值傳給PhoneDetailController中的selectedRow model,再利用ng-class上定義的表達式ng-class="{hover : selectedRow==$index},如果selectedRow的值等於$index值,則觸發hover 這個css類別,而hover就會執行其所定義的css語法.hover{background-color:lightgray}。
而在PhoneDetail.html樣板上,為了製作編輯的效果,加了將div置換為textarea的功能。這部分則把它寫成了directive,並存在directives.js :
appMoudle.directive('editable', ['$compile', function ($compile) {
return function (scope, element, attrs) {
var template, elmt;
element.bind('click', function () {
if (scope.isShow) {
elmt = element.prev('.snippet')
template = angular.element('<textarea class="snippet" rows="5" ng-model="phone.snippet" cols="100"></textarea>');
} else {
elmt = element.prev().prev('.snippet');
template = angular.element('<span class="snippet"> {{phone.snippet}} </span>');
scope.EditPhone(scope.phone);
}
scope.isShow = !(scope.isShow);
$(elmt).html($compile(template)(scope));
scope.$apply();
})
}
}])
然後把directive放到<div>中,例如 :
<divclass="row"style="text-align:left; margin-left:5px;">
<span class="snippet"> {{phone.snippet}}</span>
<span class="pointer" editable ng-show="isShow"><i class="icon-pencil"></i></span>
<span class="pointer" editable ng-hide="isShow"><iclass="icon-ok"></i></span>
</div>
利用isShow model的值傳回editable directive來控制切換鉛筆及打勾的圖示,且因為該directive放在PhoneDetailController中,所以透過editable 中的scope,能拿到在PhoneDetailController中所定義好的EditPhone方法,來執行編輯後的保存動作。在editable中也能利用jQuery指令來找到你要的元素,去do something後,再利用$compile()(scope)重新compiler DOM,再呼叫$apply檢查model內的值重新binding。
而在後端,我是用MVC4所提供的Web Api,原因是他是利用通訊協定中的GET、POST、PUT、DELETE來對應程式中的method,而AngularJS的ngResource也是,兩個剛好一搭一唱,前端直接丟物件給後端,而後端方法的參數同樣也定義為物件型別,只要兩邊的屬性一致,後端接資料完全沒問題!這太方便了,要是屬性一多,以往在web Service裡就得定義一堆的參數,web api似乎能自動轉換(懶的去證明了:P)。詳細的web api使用方法,請參閱以下連結:http://www.dotblogs.com.tw/ian/archive/2013/05/27/104900.aspx 。文中的大大是以vs2012去建立web api,但事實上vs2010也行,不過要安裝MVC4後,才有選項能選。MVC4下載路徑:http://www.microsoft.com/zh-tw/download/details.aspx?id=30683
這個CRUD的實作,其實花了我一個多禮拜才寫完,中間遇到不明之處,便網上四處爬文找資料,在摸索過程中,亦深感Angularjs設計者的巧思,讓程式具有了"美感",雖然它不像jQuery那麼"精緻",但它明確的定義了"責任"的邊界,使程式碼更具可讀性以及複用性。