Angular 基本介紹.
AngularJS 是一個 Javascript 的前端框架,藉著自訂 HTML Tag 或 Attribute 來擴展 HTML 的功能並建立成自己的 Component 來達到模組化。
另外最重要的特色為 Template 與 Model 是自動的 two-way binding,也就是異動 Model 時會自動更新 Template 上相對的欄位而反之亦然,這樣的特色得以讓工程師從 JQuery style 必須處理所有繁瑣細節中解脫,變成一次只需要專注於資料的操作或畫面的設計即可。
以下將開始依序介紹 AngularJS 的幾個重要功能,看完本教學後各位應該就能夠將 AngularJS 應用在各種專案中。
Template 與 Data Binding
<div ng-app ng-init="qty=1;cost=2">
<b>Invoice:</b>
<div>
Quantity: <input type="number" min="0" ng-model="qty">
</div>
<div>
Costs: <input type="number" min="0" ng-model="cost">
</div>
<div>
<b>Total:</b> {{qty * cost | currency}}
</div>
</div>
(Fiddle)
這是一個基本的 Model 與 Template binding 的模式,Model 在 AngularJS 被稱為 Scope,往後的說明我們都會改用 Scope 代替 Model。 裡面所看到的所有 ng-xxx 都是 AngularJS 內建的元件,又被稱為 Directive,Directive 的詳細介紹我們留到稍後再做說明,這裡僅介紹有用到的 Directive。
第一個看到的是ng-app,用來標注這個 Tag 以及其子節點需要 AngularJS 幫我們控管與處理,頁面上最少會有一個ng-app。
ng-init 內可以執行一些基本的 script,原則上不太會使用到這個 Directive,這裡是用來初始化 model 的值。
再往下看會看到兩個 input 上都有 ng-model,這是重要的 Directive,任何 type 的 input 欄位加上 ng-model 後,input 的值就會和 ng-model 裡定義的 Scope 欄位名稱自動作 binding。
最後看到 {{ }},這是個 Expressions,Expressions 內的語法跟 Javascript 差不多, 可以執行運算或呼叫 Function,和 Javascript 的差別為以下幾點:
- Context: Expressions 的 context 是 Scope,Javascript 是 window
- Null or Undefined Value 錯誤: Expressions 內如果有欄位是 null 或 undefined 不會造成錯誤,Javascript則會丟出 Error
- Flow Control: Expression 內無法使用任何條件判斷式、迴圈以及 try catch
- Expressions 內無法宣告 Function,Regexp,也無法使用逗點符號以及 void
-
Expressions 可以加上 Filters 幫 結果值做額外的處理
{{qty * cost | currency}}
這裡的 Expressions 就是把 model 內的 qty 和 cost 相乘後再交給 currency 這個 Filter 加上 $ 字號。
內建的 FIlter 和自己建立 Custom Filter 都相當簡單,請自行查詢,本教學不多做解釋。
這樣就完成了一個基本的計算功能,不需要操作 DOM 做取值與賦予值的動作,下圖是此範例的 binding 邏輯。
Controller
<script type="text/javascript">
var app = angular.module("myApp", [])
app.controller("myController", ['$scope', function($scope){
$scope.qty = 1;
$scope.cost = 2;
}])
</script>
<div ng-app="myApp" ng-controller="myController">
<b>Invoice:</b>
<div>
Quantity: <input type="number" min="0" ng-model="qty">
</div>
<div>
Costs: <input type="number" min="0" ng-model="cost">
</div>
<div>
<b>Total:</b> {{qty * cost | currency}}
</div>
</div>
(Fiddle)
雖然不一定要有 module 才能使用 controller,但為了方便往後程式擴展的維護,還是習慣都使用 module 比較好
所以首先我們給 ng-app 一個 myApp 的值,表示內容交由 myApp 這個 Module 處理
Module 的宣告方法如下:
angular.module( "moduleName", [dependencies, ...])
請記得就算不需要 reqiure 其他 module, 第二個參數也需要給空陣列[],如果沒有給第二個參數會變成get module,而不是 define module。
然後加上ng-controller,加上 ng-controller 後 Angular 會幫我們在這個 node 上建立一個 scope,稍後我們可以在 Controller 內將這個 scope inject 進來並賦予初始值。
Controller 的宣告方法如下:
app.controller( "controllerName", function(serviceName, ...){
...
})
但上述方法如果有使用 ugilfy 或 closure compiler 的話 serviceName 會編譯成亂碼而找不到正確要 inject 的 service 名稱變得無法執行,所以建議用下述比較嚴謹的方法
app.controller( "controllerName", ['serviceName', ..., function(serviceNameObj, ...){
...
}])
Angular 內建的 servie 相當多,可以自行到 API 查詢,我們需要 inject 是 $scope 這個 service,所以把他 inject 進來
app.controller("myController", ['$scope', function($scope){
...
}])
Controller 可以視為 constructor, 所以初始值的賦予都會寫在這裡,我們開始設定 scope 的初始值
$scope.qty = 1;
$scope.cost = 2;
因為 template 和 scope 是自動的 two-way binding,template 上應該一開始就會有我們在這裡設定的值了,這樣就完成了加上 Controller的動作。
下面是加上 Controller 後的邏輯示意圖
注意到這個示意圖的 ng-controller 內是用 controllerName as aliasName 的形式,所以他的 Controller 宣告內沒有使用 $scope 這個 service,而是改用 Controller 本身 this 來代替,binding邏輯是一樣的,想了解這個用法的話可以 google controller as 或者是 bindToController。
接下來我們就可以試著把 cost 的值改成由 AJAX 的方式抓取,angular 的 AJAX service 是 $http,所以 controller 可以改成
app.controller("myController", ['$scope', '$http', function($scope, $http){
$scope.qty = 1;
$http.get("urlPath").then(function(result){
$scope.cost = result.data.price;
})
}])
$http service 會回傳一個 $q 的物件,$q 是 Angular 內建的 Promise service,當 AJAX 的結果回傳後再用 then 解析出 Promise 的結果並設定到 $scope.cost 上。
Service
我們已經使用過了 Angular 內建的 $scope 和 $http service 了,現在來了解一下 service 的特性與用途。
service 的宣告方式如下:
app.factory("serviceName", ['requiredServiceName', ... , function(requiredServiceObj, ...){
...
}])
和宣告 Controller 差不多,僅差別於使用的是 factory 這個 API, 而在 function 裡回傳了什麼就決定了這個 service 被 inject 的時候可以得到的東西。
基於這個特性,我們可以用 service 來宣告物件
app.factory("myClassService", [function(){
function myClass(){ }
myClass.prototype.constructor = myClass;
return myClass;
}])
在不同的 Controller 間分享資料
app.factory("myDataService", [function(){
return {
data: [{id: 1, value: "one"}, {id: 5, value: "fifth"}]
}
}])
或者包裝複雜的邏輯,例如之前的 AJAX 取資料範例可以改成 service,我們也同時試著把 service 放在別的 module 上測試 module dependency。
angular.module("myServiceModule", [])
.factory("myAjaxService", ['$http', '$q', function($http, $q){
return {
get: function(){
return $q(function(resolve, reject){
$http.get("urlPath")
.then(function(result){
if (result.data.price <= 0){
reject("wrong price, should never be 0 or smaller than 0.")
}
resolve(result.data.price)
})
.catch(function(err){
console.log(err)
reject(err)
})
})
}
}
}])
var app = angular.module("myApp", ["myServiceModule"])
app.controller("myController", ['$scope', 'myAjaxService', function($scope, myAjaxService){
$scope.qty = 1;
myAjaxService.get().then(function(val){
$scope.cost = val;
})
}])
<div ng-app="myApp" ng-controller="myController">
<b>Invoice:</b>
<div>
Quantity: <input type="number" min="0" ng-model="qty">
</div>
<div>
Costs: <input type="number" min="0" ng-model="cost">
</div>
<div>
<b>Total:</b> {{qty * cost | currency}}
</div>
</div>
(Fiddle)
Scope
除了 Scope 與 Template 間的 two-way binding 之外,還有一些 Scope 的特性與其他常用的 API 我們也必需要知道。
繼承性
Scope 之間是有繼承關係的,每個 Angular App 都有一個叫做 rootScope 的特殊 Scope,代表的就是最底層的 Scope,而每次我們使用 ng-controller 時,Angular 就會為我們在該 node 上建立一個繼承自當前 Scope 的新 Scope,請參考以下範例。
angular.module('scopeExample', [])
.controller('FirstController', ['$scope', '$rootScope', function($scope, $rootScope) {
$rootScope.greet = 'Hello';
$scope.name = 'Nick';
}])
.controller('SecondController', ['$scope', function($scope) {
$scope.name = 'John';
}]);
<div ng-app="scopeExample" class="scope-demo">
<div ng-controller="FirstController">
{{greet}} {{name}}!
<div ng-controller="SecondController">
{{greet}} {{name}}!
</div>
</div>
</div>
(Fiddle)
執行後可以看到結果為
Hello Nick!
Hello John!
如前述,所有 Scope 都是源自於 rootScope,所以如果我們想要設定一個所有 Scope 都要有的屬性,就可以用 $rootScope 這個 service 把 rootScope inject 進來,在 rootScope 上加上 greet 的屬性值後 FirstController 和 SecondController 內就可以存取到 greet 了。
接下來我們在兩個 Controller 的 Scope 上都設定 name 這個屬性,從執行結果我們也可以得知,繼承自上層 Scope 的值是可以被 overwrite 掉的。
如果想在 SecondController 內存上層的 Scope 內容,可以透過 $scope.$parent 存取,如下例。
.controller('SecondController', ['$scope', function($scope) {
$scope.name = 'John';
}]);
<div ng-controller="SecondController">
{{greet}} {{name}}! (From {{$parent.name}})
</div>
(Fiddle)
可以得到結果
Hello John! (From Nick)
事件傳遞
scope 間的事件可以透過向下層傳遞的 $broadcast,或是向上傳遞的 $emit 來達到訊息的溝通,這也是 Controller 間常用的溝通方式。
angular.module('scopeExample', [])
.controller('FirstController', ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.$on("clicked", function(e, data){
$scope.status = data;
$scope.$broadcast("info", "acknowledge")
})
}])
.controller('SecondController', ['$scope', function($scope) {
$scope.click = function(){
$scope.$emit("clicked", "I'm clicked.")
}
$scope.$on("info", function(e, data){
$scope.info = data;
})
}]);
<div ng-app="scopeExample" class="scope-demo">
<div ng-controller="FirstController">
Message From SecondController: {{status}}
<div ng-controller="SecondController">
Message From FirstController: {{info}}
<div><button ng-click="click()">Click Me</button></div>
</div>
</div>
</div>
(Fiddle)
執行並點選 button 後可以看到
Message From SecondController: I'm clicked.
Message From FirstController: acknowledge
這個範例裡我們順便用到了 ng-click 這個 Directive, 基本上看名字就知道這個是監聽 click 的事件,和原本的 Javascript 的 event 差不多,只是 context 變成 scope 而已。
我們在 click function 內使用 $scope.$emit("eventName", yourData) 來向 FirstController 傳遞事件,而在 FirstController 內必須要用 $scope.$on("eventName", function(event, yourData){ ... }) 來接收事件。
接收事件的 function 第一個參數為 event object,當頁面上同時有使用 jQuery 時就是會若無則會 jQuery 的 event object,若無則會是 jqLite 的 event Object,所以也有 stopPropagation 可以用。
第二個參數是收到的 data,任意資料型態都可以,就再不多作說明。
$watch, $watchGroup, $watchCollection
$watch 也是 scope 裡常用的 API,使用方法如下例
angular.module('scopeExample', [])
.controller('FirstController', ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.value = 0;
$scope.$watch("value", function(newVal, oldVal){
if (newVal > 3){
$scope.value = 0;
}
})
$scope.$on("addValue", function(e, data){
$scope.value++;
})
}])
.controller('SecondController', ['$scope', function($scope) {
$scope.add = function(){
$scope.$emit("addValue");
}
}]);
<div ng-app="scopeExample" class="scope-demo">
<div ng-controller="FirstController">
Value: {{value}}
<div ng-controller="SecondController">
<div><button ng-click="add()">add</button></div>
</div>
</div>
</div>
(Fiddle)
執行後可以看到 value 加到三之後會重新歸零。
所以 $watch 就是監測 scope 屬性值的變動,可以想成是 scope 的 change 事件,$watch 的第一個參數是要監視的 scope 屬性名稱,在這裡要監視的就是 scope.value,所以使用
$scope.$watch("value", function(newValue, oldValue){ ... })
而第二個參數就是處理變動的 function,function 收到的第一個參數是變動後新的值,第二個就是變動前的值,本例就是判斷新值大於3時將 value 改回1而已。
$scope.$watch("value", function(newVal, oldVal){
if (newVal > 3){
$scope.value = 0;
}
})
這樣大家應該可以對 $watch 有基本的認識,另外一個 $watchGroup 只是一次可以監視多個屬性值,function 收到的 newValue 和 oldValue 都變成 array 而已。
angular.module('scopeExample', [])
.controller('FirstController', ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.value = 0;
$scope.value2 = 0;
$scope.$watchGroup(["value", "value2"], function(newVals, oldVals){
if (newVals[0] > 3){
$scope.value = 0;
}
})
$scope.$on("addValue", function(e, data){
$scope.value++;
})
}])
.controller('SecondController', ['$scope', function($scope) {
$scope.add = function(){
$scope.$emit("addValue");
}
}]);
<div ng-app="scopeExample" class="scope-demo">
<div ng-controller="FirstController">
Value: {{value}}
<div ng-controller="SecondController">
<div><button ng-click="add()">add</button></div>
</div>
</div>
</div>
(Fiddle)
如果要監視的屬性值是個 Object 或是 array,那最後一個 $watchCollection 就可以派上用場了。
angular.module('scopeExample', [])
.controller('FirstController', ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.items = [];
$scope.$watchCollection("items", function(newVal, oldVal){
if (newVal.length > 3){
$scope.items = [];
}
})
$scope.$on("addItem", function(e, data){
$scope.items.push({});
})
}])
.controller('SecondController', ['$scope', function($scope) {
$scope.add = function(){
$scope.$emit("addItem");
}
}]);
<div ng-app="scopeExample" class="scope-demo">
<div ng-controller="FirstController">
items length: {{items.length}}
<div ng-controller="SecondController">
<div><button ng-click="add()">add</button></div>
</div>
</div>
</div>
(Fiddle)
$watch 這個 API 在把 jQuery 元件包裝成 Directive 可以說是必定會用到的功能,在這個狀況下也一定會同時用到 $apply,這算是比較進階的課題,就留給大家自行研究了,$scope 相關 API 可以至 $scope 查詢。
Directive
最後一個議題就是我們一直說到的 Directive,我們先前已經用過一些 ng-xxx 等內建的 Directive,這些 Directie 大多著重在資料面,而沒有 Template 的部分,一般來說大部分的 Directive 都會同時包裝 Template 與 scope 而成為一個獨立的元件來使用,以下提供一個簡單的例子。
angular.module('directiveExample', [])
.controller('gridController', ['$scope', function($scope) {
$scope.add = function(){
$scope.gridData.push({});
}
$scope.save = function(){
if (!angular.isUndefined($scope.gridSave)){
$scope.gridSave($scope.gridData);
}
}
}])
.directive('myGrid', [function() {
return {
restrict: 'A',
scope: {
gridData: "=",
gridSave: "=?myGridSave"
},
controller: "gridController",
template:
'<table class="table table-striped">' +
' <thead>' +
' <tr>' +
' <td class="text-right" colspan="2">' +
' <button class="btn btn-info" ng-click="add()">add</button>' +
' <button class="btn btn-primary" ng-click="save()">save</button>' +
' </td>' +
' </tr>' +
' </thead>' +
' <tbody>' +
' <tr ng-repeat="row in gridData">' +
' <td><input type="text" ng-model="row.id"/></td>' +
' <td><input type="text" ng-model="row.desc"/></td>' +
' </tr>' +
' </tbody>' +
'</table>',
link: function(scope, el, attrs){
el.addClass("my-grid-pane");
}
}
}])
.controller('myController', ['$scope', '$window', function($scope, $window) {
$scope.data = [{id: 1, desc: "todo 1"}, {id: 2, desc: "todo 2"}];
$scope.save = function(data){
$window.alert(JSON.stringify(data));
}
}])
<div ng-app="directiveExample" class="scope-demo">
<div ng-controller="myController">
<div class="grid-pane" my-grid grid-data="data" my-grid-save="save">
</div>
</div>
</div>
(Fiddle)
這是個可以編輯的 grid 元件,寫一個可編輯的 grid 一直不是件簡單的事,但是用 angular 實作就是那麼簡單,扣掉 Template 的部分不到 30 行程式碼就完成了。
宣告 Directive 的方式也跟 Controller 差不多,只是改用 app.directive,也可以 inject 額外的 service 進來,$parse 和 $compile 這兩個 service 都算是有機會用到,有興趣的人可以自行查看, directive 的 function 原則上是回傳一個定義 directive 的 object,因為 directive 的屬性太多且有些部分比較複雜,後面只會解釋有用到的屬性,待各位對 Angular 有足夠熟悉後再去研究就可以了,在 這裡 可以查到完整的資訊。
首先看到了我宣告了一個叫 myGrid 的 directive,這裡要注意名稱大小寫和實際要使用時會有點不同,在 template 上引用這個元件時要用 my-grid。
.directive('myGrid', [function() { ...} ])
<div class="grid-pane" my-grid grid-data="data" my-grid-save="save"></div>
這是因為在 html 上 tag name 和 attribute 是都是視為小寫的,所以在 template 上使用時大小的字要改成小寫並在前面加上 - 號,Angular 內部會自己轉換成正確的 directive 名稱。
接下來就開始設定屬性,我們先接紹完用到的屬性後再開始解釋程式碼:
restrict
決定引用 directive 的方式,可以用 tag name (E) 也可以用 attribute (A),其他還有 class name 和 comment,但一般不太會用到,預設值是 E,附上官網說明應該就很清楚了。
E - Element name (default): <my-directive></my-directive>
A - Attribute (default): <div my-directive="exp"></div>
C - Class: <div class="my-directive: exp;"></div>
M - Comment: <!-- directive: my-directive exp -->
scope
scope 比較複雜,directive 的 scope 值可以定義成三種型態
- false 表示不建立新的 scope,直接使用當前的 scope。
- true 建立一個繼承至當前 scope 的新 scope。
- {} Object 如果給的是一個 object,會建立一個不繼承任何 scope 的 isolate scope
前兩個都很單純,應該就不用再解釋,而 isolate scope 有幾個特殊的符號要熟記,這裡也用官網的例子。
@ or @attr or @?attr
表示 scope 上的屬性值要 bind 到指定的 dom 上的 attribute 值,如
<widget my-attr="hello {{name}}">
scope: { localName:'@myAttr' }
當前 name 的值是 Nick,那麼 directive 裡面的 scope.localName 就會是 hello Nick,name 改變時 localName 也會自動改變,如果 dom 上 attribute 的名稱和 scope 的名稱相同,就可以用偷懶的寫法,dom attribute 的名稱可以省略。
<widget local-name="hello {{name}}">
scope: { localName:'@' }
另外也要注意 dom 上的 attribute 名稱和 scope attribute 大小寫之間的關係是和 directive 名稱的特性是一樣的,取不到值時先檢查 dom 是不是上忘記把大寫改成 -小寫 可以省下你不少時間。
加上 ? 表示這個屬性是 optional。
= or =attr or @?attr
bi-direction binding,也就是在 directive 內或外改變這個值都會同時更改另一方。
<widget my-attr="parentModel">
scope: { localModel:'=myAttr' }
這裡要注意 dom 上不是用 expressions {{}},而是直接用屬性名稱就好。
& or &attr or &?attr
定義方式差不多。
<widget my-attr="count = count + value">
scope: { localFn:'&myAttr' }
看官網範例應該就能了解用法。
provides a way to execute an expression in the context of the parent scope
if the expression is increment(amount)
then we can specify the amount value by calling the localFn as localFn({amount: 22})
controller
coontroller名稱,不一定要另外宣告 Controller,也可以直接寫在 link 裡就好,看個人習慣與是否有用到 require 這個屬性,被 required 的 directive 基本上 Controller 都需要另外拉出來。
template
如果是用字串直接內嵌的 html 就用 template,如果 html 是在另外一個 xxx.html 檔案就要改用 templateUrl,或者有用 grunt module html2js 的話也是用 templateUrl。
link
link 是 scope 已經 bind 到 template 後會被執行的 function,在這裡才可以開始做 dom 的操作或事件動作,例如一個可以拖拉改變位置的元件就會在這裡寫mousedown mousemove mouseup時改變位置的邏輯。 function 接收5個值,常用的是前面三個。
function link(scope, el, attrs, controller, transcludeFn) { ... }
scope 就是 directive 的 scope
el 就是 directive 本身最上層的 dom
attrs 就是 el 上所有的 attributes,Angular 幫我們變成一個 object 方便存取,所以也可以透過 attrs 手動做額外的 binding。
另外兩個算進階用法,請自行到官網查詢。
以上解釋完用到屬性,接著要繼續解釋我們的 myGrid 。
restrict: 'A'
設定成只能用 attribute 的方式引用,沒特別原因,一般都是用 EA 比較多。
scope: {
gridData: "=",
gridSave: "=?myGridSave"
}
scope 使用 isolate scope,兩個屬性都是 bi-direction binding,gridData 是資料來源,在要引用 myGrid 的 node 上必須同時設定這個屬性,不然會丟出 error,我們要把 myController scope 上的 data binding 到 myGrid 上,所以也要設定好測試的用的 data。
<div class="grid-pane" my-grid grid-data="data"></div>
.controller('myController', ['$scope', function($scope) {
$scope.data = [{id: 1, desc: "todo 1"}, {id: 2, desc: "todo 2"}];
}]
再來準備 bind 第二個屬性 gridSave,這個是 optional 的屬性,沒有設定也不會出現 eroor,先將這個屬性加上。
<div class="grid-pane" my-grid grid-data="data" my-grid-save="save"></div>
.controller('myController', ['$scope', '$window', function($scope, $window) {
$scope.data = [{id: 1, desc: "todo 1"}, {id: 2, desc: "todo 2"}];
$scope.save = function(data){
$window.alert(JSON.stringify(data));
}
}])
然後設定 controller 使用 gridController
controller: "gridController"
.controller('gridController', ['$scope', function($scope) {
$scope.add = function(){
$scope.gridData.push({});
}
$scope.save = function(){
if (!angular.isUndefined($scope.gridSave)){
$scope.gridSave($scope.gridData);
}
}
}])
以及 template
template:
'<table class="table table-striped">' +
' <thead>' +
' <tr>' +
' <td class="text-right" colspan="2">' +
' <button class="btn btn-info" ng-click="add()">add</button>' +
' <button class="btn btn-primary" ng-click="save()">save</button>' +
' </td>' +
' </tr>' +
' </thead>' +
' <tbody>' +
' <tr ng-repeat="row in gridData">' +
' <td><input type="text" ng-model="row.id"/></td>' +
' <td><input type="text" ng-model="row.desc"/></td>' +
' </tr>' +
' </tbody>' +
'</table>'
先看到 tbody 中有一個 ng-repeat,ng-repeat 會依序取出 collection 中的 item,並複製一個 ng-repeat 所在的 dom,然後建立一個新的 scope bind 上去,並把 item 加到 scope 上,在這個 dom 上就可以存取到 item 的值,當然我們把 item 的名稱取名叫 row,存取時就要用 row.id 和 row.desc。
'<tr ng-repeat="row in gridData">' +
' <td><input type="text" ng-model="row.id"/></td>' +
' <td><input type="text" ng-model="row.desc"/></td>' +
'</tr>'
template 中另外看到有兩個 button,add 按了之後要新增一筆空資料,save 按了應該要呼叫 binding 到 gridSave 上的外層 scope 屬性,所以我們在 gridController 中要處理這段邏輯。
'<button class="btn btn-info" ng-click="add()">add</button>'
$scope.add = function(){
$scope.gridData.push({});
}
基本上 add 事件就對 gridData push 一個 object 就可以了。
'<button class="btn btn-primary" ng-click="save()">save</button>'
$scope.save = function(){
if (!angular.isUndefined($scope.gridSave)){
$scope.gridSave($scope.gridData);
}
}
save 事件我們想要把當前的資料丟回給 binding 到 gridSave 上的外部 scope function, 但這個屬性是 optional,所以要先判斷是不是 undefined 才可以呼叫,實際上 gridData 是 bi-direction binding,所以呼叫 gridSave 時是可以不用給 $scope.gridData 這個參數的,因為外層 scope 上的 data 也會一起變動,不用特別處理也可以取得當前 grid 的值,這裡只是讓大家知道 可以 bind function 進來也可以正常給參數的。
外部的 scope 上定義的 function 收到呼叫後跳出 alert 顯示出資料內容。
$scope.save = function(data){
$window.alert(JSON.stringify(data));
}
注意到我們 inject 了 $window 這個 service ,Angular 中若要使用 javascript 的 window 這個物件上的 API 都會使用 $window 這個 service,這在寫 unit test 的時候比較容易做 mock,除此之外 setTimeout、setInterval、log、location 等都有對應的 angular service, 以 setTimeout 為例,對應的 service 為 $timeout, 若直接使用 window.setTimeout 且在其中有改變 scope 的屬性值,你會發現畫面上的欄位沒有跟著變動,因為不在 Angular digest cycle 中做的數值修改是不會被偵測到的,包含先前介紹過的 $watch 也會沒有反應,所以要特別注意需要使用 window 下的原生 API 時,要先確認一下是否有對應的 anguler service 可以使用,不然就是在修改值後要加上 $scope.$apply() 通知 Angular 有變動發生。
詳細可以參照官網 此頁 最下面的 Integration with the browser event loop 章節內有說明。
最後是
link: function(scope, el, attrs){
el.addClass("my-grid-pane");
}
這裡其實沒什麼必要加,因為我們沒有要操作 dom,僅增加一個 class 示意。
結語
Angular 的邏輯和 jQuery 或其他 framework 相差甚多,尤其熟悉 jQuery 的人可能想法會常常轉不過來而不知道同樣的功能要怎麼用 Directive 實作,而 Directive 可以說是 Angular 的精華所在,建議大家可以多看看 github 上的各種優秀 directive 才能更加地熟悉精進,例如 Angular 版的 bootstrap 就有各種元件的實作,大部分的 Directive 都是 100 ~ 200 行而已,相信閱讀也不會太困難。