初學 AngularJS 的人,寫到 ngController 之後一定會覺得 AngularJS 的 controller 怎麼這麼簡單,只要宣告一個 function 就馬上可以用了,而且 function 裡面的參數還會經由 AngularJS 自動注入物件。例如 $scope、$http、$window、$log、…等等。不過,這種註冊 controller 的方式雖然簡單,卻還是有些缺點,例如這些 function 宣告不能被 最小化 (Minification),否則 function 內的區域變數被改成 a, b, c 之類的,AngularJS 就無法自動注入物件了,因此必須進一步學習更多的宣告方式,藉此解決 相依注入 (Dependency Injection) 的問題!
我們就來看看各種不同的 ngController 宣告方法:
1. 直接宣告一個建構式函式 (constructor)
範例:[ http://jsbin.com/utubis/1/edit ]
直接宣告建構式函式的方法,AngularJS 會自動注入註冊在 module 內的任意 service/provider 物件,是個非常便捷的寫法,不用太多知識背景支持,隨時可以在 View (HTML) 裡使用這個 controller 建構式。
function MainCtrl($scope) {
$scope.name = 'Will';
}
其對應的 View (HTML) 如下範例:
<!DOCTYPE html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
<div ng-controller="MainCtrl">
{{ name }}
</div>
</body>
</html>
不過,這寫法有點小問題,那就是 JavaScript 最小化 (Minification) 的問題,這個函式,經過最小化處理後的結果為如下,很明顯的,$scope 因為是區域變數,所以被最小化了,變數名稱被改成了 a,而 AngularJS 在執行相依注入時,自然就找不到 $scope 物件參考,導致程式無法正確執行 :
function MainCtrl(a){a.name="Will"};
2. 直接宣告一個建構式函式 (constructor) 並擴增一個 $inject 屬性指定相依注入的物件名稱
範例:[ http://jsbin.com/utubis/2/edit ]
要解決直接宣告建構式函式卻無法搭配 JS 最小化 的問題,AngularJS 允許你直接對該建構式擴充,只要新增一個 $inject 屬性,並指派一個字串陣列,即可提示 AngularJS 在執行相依注入時依照 $inject 屬性指定的陣列順序注入到建構式的參數中 (如果一個以上的話,是依據陣列中宣告的順序喔!)
以下程式碼範例中,在 MainCtrl 控制器的第一個參數 a 就代表著 $scope 物件參考,而 b 就代表者 $http 物件參考,其物件內容會由 AngularJS 自動注入:
function MainCtrl(a, b) {
a.name = 'Will';
}
MainCtrl.$inject = ['$scope', '$http'];
3. 透過一個自訂模組 (module) 並將控制器宣告註冊在該模組裡
範例:[ http://jsbin.com/utubis/3/edit ]
透過自訂模組 (module) 註冊控制器宣的方法,其實跟直接宣告 function 的做法差不多,你可以比對一下兩個原始碼的差異。但透過自訂模組的方式註冊控制器,是比較模組化的寫法,對日益複雜的 AngularJS 專案來說,會比較容易管理。而透過自訂模組物件的 controller 方法,就可以註冊控制器,如下程式碼範例所示,第一個參數就是 ngController 控制器的名稱,第二個參數直接傳入剛剛我們先前範例的中出現的建構式:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function MainCtrl($scope) {
$scope.name = 'Will';
});
當你自訂了 myApp 模組,也代表著你所定義的 controller 只會存在於 myApp 模組裡,所以你的 HTML 必須修改 ng-app 的屬性值,並指派 myApp 為屬性值,如下範例:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
<div ng-controller="MainCtrl">
{{ name }}
</div>
</body>
</html>
不過,這樣的定義方式,一樣會遇到當 JS 最小化之後,相依注入會失敗的問題,這時我們就可以用下一種語法來註冊控制器。
4. 透過一個自訂模組 (module) 並將控制器宣告註冊在該模組裡,並且指定相依注入的物件名稱
範例:[ http://jsbin.com/utubis/4/edit ]
指定相依注入的方法,可以直接跟上述第3種宣告方法整合在一起,如下程式碼片段,你只要把第2個參數改成一個陣列,陣列中「最後一個元素」是控制器的建構式,其他的都是要執行相依注入的物件名稱即可,就可以解決這個 JS 最小化的問題。 ( 記得: 建構式函式中的 $scope 參數可以改成任意名稱 )
var app = angular.module('myApp', []);
app.controller('MainCtrl', ['$scope', function MainCtrl($scope) {
$scope.name = 'Will';
}]);
5. 透過 $controllerProvider 註冊控制器
範例:[ http://jsbin.com/utubis/5/edit ]
上述幾種都算是常用的控制器註冊方法,但如果你想使用更低階的 $controllerProvider 來註冊控制器的話,則必須透過自訂模組物件的 config 方法,來配置該模組,以下為程式碼範例:
var app = angular.module('myApp', []);
app.config(['$controllerProvider', function($controllerProvider) {
$controllerProvider.register('MainCtrl', ['$scope', function ($scope) {
$scope.name = 'Will';
}]);
}]);
從這個範例,你也應該可以看到 Module.config 這段,後面接的也是一個陣列,陣列中前面的元素也都是要被注入的物件名稱,最後一個參數才是建構式函式,其實包括 factory, service 也都是一樣的用法,都可以用這種方式執行相依注入的宣告,建議各位可以養成習慣用這種方式來實作相依注入。
[ 2013-07-23 補充:感謝 零時政府 的 clkao 提示以下方法 ]
6. 透過一個自訂模組 (module) 並透過一個 controller 方法傳入匿名物件批次建立多個控制器
範例:[ http://jsbin.com/utubis/6/edit ]
另外一個宣告 controller 的方法,是傳入一個匿名物件,每個屬性就是一組 controller 定義,屬性名稱就是 controller 名稱,屬性值可以是一個建構式函式,也可以是一個陣列,其定義規則跟上述說明都一樣。
var app = angular.module('myApp', []);
app.controller({
'MainCtrl': ['$scope', function MainCtrl($scope) {
$scope.name = 'Will';
}
],
'MainCtrl2': ['$scope', function MainCtrl($scope) {
$scope.name = 'Will2';
}
]
});
相關連結