The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

關於 AngularJS 控制器 (ngController) 的多種宣告方法

初學 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';
                           }
                ]
});

相關連結