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

初學 AngularJS 的人，寫到 [ngController](http://docs.angularjs.org/api/ng.directive:ngController) 之後一定會覺得 AngularJS 的 controller 怎麼這麼簡單，只要宣告一個 function 就馬上可以用了，而且 function 裡面的參數還會經由 AngularJS 自動注入物件。例如 [$scope](http://docs.angularjs.org/api/ng.$rootScope.Scope)、[$http](http://docs.angularjs.org/api/ng.$http)、[$window](http://docs.angularjs.org/api/ng.$window)、[$log](http://docs.angularjs.org/api/ng.$log)、…等等。不過，這種註冊 controller 的方式雖然簡單，卻還是有些缺點，例如這些 function 宣告不能被 **最小化** (Minification)，否則 function 內的區域變數被改成 a, b, c 之類的，AngularJS 就無法自動注入物件了，因此必須進一步學習更多的宣告方式，藉此解決 **相依注入** (Dependency Injection) 的問題！

我們就來看看各種不同的 ngController 宣告方法：

**1\. 直接宣告一個建構式函式 (constructor)  
**範例:\[ [http://jsbin.com/utubis/1/edit](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](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](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](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](http://jsbin.com/utubis/5/edit) \]

上述幾種都算是常用的控制器註冊方法，但如果你想使用更低階的 [$controllerProvider](http://docs.angularjs.org/api/ng.$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 補充：感謝 [零時政府](http://g0v.tw/) 的 [clkao](https://www.facebook.com/clkao) 提示以下方法 \]

**6\. 透過一個自訂模組 (module) 並透過一個 controller 方法傳入匿名物件批次建立多個控制器  
**範例:\[ [http://jsbin.com/utubis/6/edit](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';
                           }
                ]
});
```

**相關連結**

-   [Understanding the Controller Component](http://code.angularjs.org/1.1.5/docs/guide/dev_guide.mvc.understanding_controller)
-   [Injecting Services Into Controllers](http://code.angularjs.org/1.1.5/docs/guide/dev_guide.services.injecting_controllers)
-   [$controllerProvider (service in module ng )](http://docs.angularjs.org/api/ng.$controllerProvider)
-   [Module (Type in module ng )](http://docs.angularjs.org/api/angular.Module)
