很好的angularjs教程

AngularJS學習筆記

  • 2014-01-22 21:48 更新javascript

  • 鄒業盛css

  1. 關於AngularJShtml

  2. 關於本文檔前端

  3. AngularJS學習筆記

  4. 2014-01-22 21:48 更新java

  5. 鄒業盛node

  6. 關於AngularJSjquery

  7. 關於本文檔git

  8. 開始的例子angularjs

  9. 依賴注入github

  10. 做用域

  11. 數據綁定與模板

    1. 6.1. 數據->模板

    2. 6.2. 模板->數據

    3. 6.3. 數據->模板->數據->模板

  12. 模板

    1. 7.1. 定義模板內容

    2. 7.2. 內容渲染控制

    3. 7.3. 節點控制

    4. 7.4. 事件綁定

    5. 7.5. 表單控件

  13. 模板中的過濾器

    1. 8.1. 排序 orderBy

    2. 8.2. 過濾列表 filter

    3. 8.3. 其它

    4. 8.4. 例子:表頭排序

    5. 8.5. 例子:搜索

  14. 錨點路由

    1. 9.1. 路由定義

    2. 9.2. 參數定義

    3. 9.3. 業務處理

  15. 定義模板變量標識標籤

  16. AJAX

    1. 11.1. HTTP請求

    2. 11.2. 廣義回調管理

  17. 工具函數

    1. 12.1. 上下文綁定

    2. 12.2. 對象處理

    3. 12.3. 類型斷定

  18. 其它服務

    1. 13.1. 日誌

    2. 13.2. 緩存

    3. 13.3. 計時器

    4. 13.4. 表達式函數化

    5. 13.5. 模板單獨使用

  19. 自定義模塊和服務

    1. 14.1. 模塊和服務的概念與關係

    2. 14.2. 定義模塊

    3. 14.3. 定義服務

    4. 14.4. 引入模塊並使用服務

  20. 附加模塊 ngResource

    1. 15.1. 使用引入與總體概念

    2. 15.2. 基本定義

    3. 15.3. 基本使用

    4. 15.4. 定義和使用時的佔位量

    5. 15.5. 實例

  21. AngularJS與其它框架的混用(jQuery, Dojo)

  22. 自定義過濾器

  23. 自定義指令directive

    1. 18.1. 指令的使用

    2. 18.2. 指令的執行過程

    3. 18.3. 基本的自定義方法

    4. 18.4. 屬性值類型的自定義

    5. 18.5. Compile的細節

    6. 18.6. transclude的細節

    7. 18.7. 把節點內容做爲變量處理的類型

    8. 18.8. 指令定義時的參數

    9. 18.9. Attributes的細節

    10. 18.10. 預約義的 NgModelController

    11. 18.11. 預約義的 FormController

    12. 18.12. 示例:文本框

    13. 18.13. 示例:模板控制語句 for

    14. 18.14. 示例:模板控制語句 if/else

  24. 本文的內容是在 1.0.x 版本之下完成的。

  25. 1. 關於AngularJS

  26. AngularJS 是 Google 開源出來的一套 js 工具。下面簡稱其爲 ng 。這裏只說它是「工具」,沒說它是完整的「框架」,是由於它並非定位於去完成一套框架要作的事。更重要的,是它給咱們揭示了一種新的應用組織與開發方式。

  27. ng 最讓我稱奇的,是它的數據雙向綁定。其實想一想,咱們一直在提數據與表現的分離,可是這裏的「雙向綁定」從某方面來講,是把數據與表現徹底綁定在一塊兒——數據變化,表現也變化。反之,表現變化了,內在的數據也變化。有過開發經驗的人能體會到這種機制對於前端應用來講,是頗有必要的,能帶來維護上的巨大優點。固然,這裏的綁定與提倡的分離並非矛盾的。

  28. ng 能夠和 jQuery 集成工做,事實上,若是沒有 jQuery , ng 本身也作了一個輕量級的 jQuery ,主要實現了元素操做部分的 API 。

  29. 關於 ng 的幾點:

  30. 對 IE 方面,它兼容 IE8 及以上的版本。

  31. 與 jQuery 集成工做,它的一些對象與 jQuery 相關對象表現是一致的。

  32. 使用 ng 時不要冒然去改變相關 DOM 的結構。

  33. 2. 關於本文檔

  34. 這份文檔如其名,是我本身學習 ng 的過程記錄。只是過程記錄,沒有刻意像教程那樣去作。因此呢,從前至後,中間難免有一些概念不清不明的地方。由於事實上,在某個階段對於一些概念原本就不可能明白。因此,整個過程只求在形式上的能用便可——直到最後的「自定義」那幾章,特別是「自定義指令」,那幾章過完,你才能看清 ng 原本的面貌。前面就不要太糾結概念,本質,知道怎麼用就好。

  35. 3. 開始的例子

  36. 咱們從一個完整的例子開始認識 ng :

  37.  1   <!DOCTYPE html> 2   <html> 3   <head> 4   <meta charset="utf-8" /> 5    6   <title>試驗</title> 7    8   <script type="text/javascript" src="jquery-1.8.3.js"></script> 9   <script type="text/javascript" src="angular.js"></script>10   11   </head>12   <body>13     <div ng-controller="BoxCtrl">14       <div style="width: 100px; height: 100px; background-color: red;"15            ng-click="click()"></div>16       <p>{{ w }} x {{ h }}</p>17       <p>W: <input type="text" ng-model="w" /></p>18       <p>H: <input type="text" ng-model="h" /></p>19     </div>20   21   22   <script type="text/javascript" charset="utf-8">23   24   25   var BoxCtrl = function($scope, $element){26   27     //$element 就是一個 jQuery 對象28     var e = $element.children().eq(0);29     $scope.w = e.width();30     $scope.h = e.height();31   32     $scope.click = function(){33       $scope.w = parseInt($scope.w) + 10;34       $scope.h = parseInt($scope.h) + 10;35     }36   37     $scope.$watch('w',38       function(to, from){39         e.width(to);40       }41     );42   43     $scope.$watch('h',44       function(to, from){45         e.height(to);46       }47     );48   }49   50   angular.bootstrap(document.documentElement);51   </script>52   </body>53   </html>
  38. 從上面的代碼中,咱們看到在一般的 HTML 代碼當中,引入了一些標記,這些就是 ng 的模板機制,它不光完成數據渲染的工做,還實現了數據綁定的功能。

  39. 同時,在 HTML 中的自己的 DOM 層級結構,被 ng 利用起來,直接做爲它的內部機制中,上下文結構的判斷依據。好比例子中 p 是 div 的子節點,那麼 p 中的那些模板標記就是在 div 的 Ctrl 的做用範圍以內。

  40. 其它的,也一樣寫一些 js 代碼,裏面重要的是做一些數據的操做,事件的綁定定義等。這樣,數據的變化就會和頁面中的 DOM 表現聯繫起來。一旦這種聯繫創建起來,也即完成了咱們所說的「雙向綁定」。而後,這裏說的「事件」,除了那些「點擊」等一般的 DOM 事件以外,咱們還更關注「數據變化」這個事件。

  41. 最後,可使用:

  42.   angular.bootstrap(document.documentElement);
  43. 來把整個頁面驅動起來了。(你能夠看到一個可被控制大小的紅色方塊)

  44. 更完整的方法是定義一個 APP :

  45.  1   <!DOCTYPE html> 2   <html ng-app="MyApp"> 3   <head> 4   <meta charset="utf-8" /> 5    6   <title>數據正向綁定</title> 7    8   <script type="text/javascript" src="jquery-1.8.3.js"></script> 9   <script type="text/javascript" src="angular.js"></script>10   11   </head>12   <body>13   14   <div ng-controller="TestCtrl">15     <input type="text" value="" id="a" />16   </div>17   18   19   <script type="text/javascript">20   var TestCtrl = function(){21     console.log('ok');22   }23   24   //angular.bootstrap(document.documentElement);25   angular.module('MyApp', [], function(){console.log('here')});26   </script>27   28   </body>29   </html>
  46. 這裏說的一個 App 就是 ng 概念中的一個 Module 。對於 Controller 來講, 若是不想使用全局函數,也能夠在 app 中定義:

  47.   var app = angular.module('MyApp', [], function(){console.log('here')});
      app.controller('TestCtrl',    function($scope){
          console.log('ok');
        }
      );
  48. 上面咱們使用 ng-app 來指明要使用的 App ,這樣的話能夠把顯式的初始化工做省了。通常完整的過程是:

  49.   var app = angular.module('Demo', [], angular.noop);
      angular.bootstrap(document, ['Demo']);
  50. 使用 angular.bootstrap 來顯示地作初始化工具,參數指明瞭根節點,裝載的模塊(能夠是多個模塊)。

  51. 4. 依賴注入

  52. injector , 我從 ng 的文檔中得知這個概念,以後去翻看源碼時瞭解了一下這個機制的工做原理。感受就是雖然與本身的所想僅差那麼一點點,但就是這麼一點點,讓我感慨想象力之神奇。

  53. 先看咱們以前代碼中的一處函數定義:

  54.   var BoxCtrl = function($scope, $element){}
  55. 在這個函數定義中,注意那兩個參數: $scope , $element ,這是兩個頗有意思的東西。總的來講,它們是參數,這沒什麼可說的。但又不只僅是參數——你換個名字代碼就不能正常運行了。

  56. 事實上,這兩個參數,除了完成「參數」的自己任務以外,還做爲一種語法糖完成了「依賴聲明」的任務。原本這個函數定義,完整的寫法應該像 AMD 聲明同樣,寫成:

  57.   var BoxCtrl = ['$scope', '$element', function(s, e){}];
  58. 這樣就很明顯,表示有一個函數,它依賴於兩個東西,而後這兩個東西會依次做爲參數傳入。

  59. 簡單起見,就寫成了一個函數定義本來的樣子,而後在定義參數的名字上做文章,來起到依賴聲明的做用。

  60. 在處理時,經過函數對象的 toString() 方法能夠知道這個函數定義代碼的字符串表現形式,而後就知道它的參數是 $scope 和 $element 。經過名字判斷出這是兩個外部依賴,而後就去獲取資源,最後把資源做爲參數,調用定義的函數。

  61. 因此,參數的名字是不能隨便寫的,這裏也充分利用了 js 的特色來儘可能作到「檢討」了。

  62. 在 Python 中受限於函數名的命名規則,寫出來不太好看。不過也得利於檢討機制,作到這點也很容易:

  63.   # -*- coding: utf-8 -*-
      
      def f(Ia, Ib):      print Ia, Ib
      
      args = f.func_code.co_varnames
      SRV_MAP = {      'Ia': '123',      'Ib': '456',
      }
      
      srv = {}  for a in args:      if a in SRV_MAP:
              srv[a] = SRV_MAP[a]
      f(**srv)
  64. 5. 做用域

  65. 這裏提到的「做用域」的概念,是一個在範圍上與 DOM 結構一致,數據上相對於某個 $scope 對象的屬性的概念。咱們仍是從 HTML 代碼上來入手:

  66.   <div ng-controller="BoxCtrl">
        <div style="width: 100px; height: 100px; background-color: red;"
             ng-click="click()">
        </div>
        <p>{{ w }} x {{ h }}</p>
        <p>W: <input type="text" ng-model="w" /></p>
        <p>H: <input type="text" ng-model="h" /></p>
      </div>
  67. 上面的代碼中,咱們給一個 div 元素指定了一個 BoxCtrl ,那麼, div 元素以內,就是 BoxCtrl 這個函數運行時, $scope 這個注入資源的控制範圍。在代碼中咱們看到的 click() , w , h 這些東西,它們原本的位置對應於 $scope.click , $scope.w , $scope.h 。

  68. 咱們在後面的 js 代碼中,也能夠看到咱們就是在操做這些變量。依賴於 ng 的數據綁定機制,操做變量的結果直接在頁面上表現出來了。

  69. 6. 數據綁定與模板

  70. 我糾結了半天,「數據綁定」與「模板」這兩個東西還真沒辦法分開來講。由於數據綁定須要以模板爲載體,離開了模板,數據還綁個毛啊。

  71. ng 的一大特色,就是數據雙向綁定。雙向綁定是一體,爲了描述方便,下面分別介紹。

  72. 6.1. 數據->模板

  73. 數據到表現的綁定,主要是使用模板標記直接完成的:

  74.   <p>{{ w }} x {{ h }}</p>
  75. 使用 {{ }} 這個標記,就能夠直接引用,並綁定一個做用域內的變量。在實現上, ng 自動建立了一個 watcher 。效果就是,無論由於什麼,若是做用域的變量發生了改變,咱們隨時可讓相應的頁面表現也隨之改變。咱們能夠看一個更純粹的例子:

  76.   <p id="test" ng-controller="TestCtrl">{{ a }}</p>
      
      <script type="text/javascript">
      var TestCtrl = function($scope){
        $scope.a = '123';
      }
      angular.bootstrap(document.documentElement);
  77. 上面的例子在頁面載入以後,咱們能夠在頁面上看到 123 。這時,咱們能夠打開一個終端控制器,輸入:

  78.   $('#test').scope().a = '12345';
      $('#test').scope().$digest();
  79. 上面的代碼執行以後,就能夠看到頁面變化了。

  80. 對於使用 ng 進行的事件綁定,在處理函數中就不須要去關心 $digest() 的調用了。由於 ng 會本身處理。源碼中,對於 ng 的事件綁定,真正的處理函數不是指定名字的函數,而是通過 $apply() 包裝過的一個函數。這個 $apply() 作的一件事,就是調用根做用域 $rootScope 的 $digest() ,這樣整個世界就清淨了:

  81.   <p id="test" ng-controller="TestCtrl" ng-click="click()">{{ a }}</p>
      
      <script type="text/javascript" charset="utf-8">
      var TestCtrl = function($scope){
        $scope.a = '123';
      
        $scope.click = function(){
          $scope.a = '456';
        }
      }
      angular.bootstrap(document.documentElement);
  82. 那個 click 函數的定義,綁定時變成了相似於:

  83.   function(){
        $scope.$apply(
          function(){
            $scope.click();
          }
        )
      }
  84. 這裏的 $scope.$apply() 中作的一件事:

  85.   $rootScope.$digest();
  86. 6.2. 模板->數據

  87. 模板到數據的綁定,主要是經過 ng-model 來完成的:

  88.   <input type="text" id="test" ng-controller="TestCtrl" ng-model="a" />
      
      <script type="text/javascript" charset="utf-8">
      var TestCtrl = function($scope){
        $scope.a = '123';
      }
  89. 這時修改 input 中的值,而後再在控制終端中使用:

  90.   $('#test').scope().a
  91. 查看,發現變量 a 的值已經更改了。

  92. 實際上, ng-model 是把兩個方向的綁定都作了。它不光顯示出變量的值,也把顯示上的數值變化反映給了變量。這個在實現上就簡單多了,只是綁定 change 事件,而後作一些賦值操做便可。不過 ng 裏,還要區分對待不一樣的控件。

  93. 6.3. 數據->模板->數據->模板

  94. 如今要考慮的是一種在現實中很廣泛的一個需求。好比就是咱們能夠輸入數值,來控制一個矩形的長度。在這裏,數據與表現的關係是:

  95. 長度數值保存在變量中

  96. 變量顯示於某個 input 中

  97. 變量的值便是矩形的長度

  98. input 中的值變化時,變量也要變化

  99. input 中的值變化時,矩形的長度也要變化

  100. 固然,要實現目的在這裏可能就不止一種方案了。按照之前的作法,很天然地會想法,綁定 input的 change 事件,而後去作一些事就行了。可是,咱們前面提到過 ng-model 這個東西,利用它就能夠在不手工處理 change 的條件下完成數據的展示需求,在此基礎之上,咱們還須要作的一點,就是把變化後的數據應用到矩形的長度之上。

  101. 最開始,咱們面對的應該是這樣一個東西:

  102.   <div ng-controller="TestCtrl">
        <div style="width: 100px; height: 10px; background-color: red"></div>
        <input type="text" name="width" ng-model="width" />
      </div>
      
      <script type="text/javascript" charset="utf-8">  var TestCtrl = function($scope){
        $scope.width = 100;
      }
      angular.bootstrap(document.documentElement);
      </script>
  103. 咱們從響應數據變化,但又不使用 change 事件的角度來看,能夠這樣處理寬度變化:

  104.   var TestCtrl = function($scope, $element){
        $scope.width = 100;
        $scope.$watch('width',      function(to, from){
            $element.children(':first').width(to);
          }
        );
      }
  105. 使用 $watch() 來綁定數據變化。

  106. 固然,這種樣式的問題,有更直接有效的手段, ng 的數據綁定老是讓人驚異:

  107.   <div ng-controller="TestCtrl">
        <div style="width: 10px; height: 10px; background-color: red" ng-style="style">
        </div>
        <input type="text" name="width" ng-model="style.width" />
      </div>
      
      
      <script type="text/javascript" charset="utf-8">  var TestCtrl = function($scope){
        $scope.style = {width: 100 + 'px'};
      }
      angular.bootstrap(document.documentElement);
      </script>
  108. 7. 模板

  109. 前面講了數據綁定以後,如今能夠單獨講講模板了。

  110. 做爲一套能稱之謂「模板」的系統,除了能幹一些模板的常規的事以外(好吧,即便是常規的邏輯判斷如今它也作不了的),配合做用域 $scope 和 ng 的數據雙向綁定機制, ng 的模板系統就變得比較神奇了。

  111. 7.1. 定義模板內容

  112. 定義模板的內容如今有三種方式:

  113. 在須要的地方直接寫字符串

  114. 外部文件

  115. 使用 script 標籤訂義的「內部文件」

  116. 第一種不須要多說。第二種和第三種均可以和 ng-include 一塊兒工做,來引入一段模板。

  117. 直接引入同域的外部文件做爲模板的一部分:

  118.   <div ng-include src="'tpl.html'">
      </div>
      
      <div ng-include="'tpl.html'">
      </div>
  119. 注意, src 中的字符串會做爲表達式處理(能夠是 $scope 中的變量),因此,直接寫名字的話須要使用引號。

  120. 引入 script 定義的「內部文件」:

  121.   <script type="text/ng-template" id="tpl">
      here, {{ 1 + 1 }}
      </script>
      
      <div ng-include src="'tpl'"></div>
  122. 配合變量使用:

  123.   <script type="text/ng-template" id="tpl">
      here, {{ 1 + 1 }}
      </script>
      
      <a ng-click="v='tpl'">Load</a>
      <div ng-include src="v"></div>
  124. 7.2. 內容渲染控制

  125. 7.2.1. 重複 ng-repeat

  126. 這算是惟一的一個控制標籤麼……,它的使用方法類型於:

  127.   <div ng-controller="TestCtrl">
        <ul ng-repeat="member in obj_list">
          <li>{{ member }}</li>
        </ul>
      </div>
      
      
      var TestCtrl = function($scope){
        $scope.obj_list = [1,2,3,4];
      }
  128. 除此以外,它還提供了幾個變量可供使用:

  129. $index 當前索引

  130. $first 是否爲頭元素

  131. $middle 是否爲非頭非尾元素

  132. $last 是否爲尾元素

  133.   <div ng-controller="TestCtrl">
        <ul ng-repeat="member in obj_list">
          <li>{{ $index }}, {{ member.name }}</li>
        </ul>
      </div>
      
      var TestCtrl = function($scope){
        $scope.obj_list = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
      }
  134. 7.2.2. 賦值 ng-init

  135. 這個指令能夠在模板中直接賦值,它做用於 angular.bootstrap 以前,而且,定義的變量與 $scope做用域無關。

  136.   <div ng-controller="TestCtrl" ng-init="a=[1,2,3,4];">
        <ul ng-repeat="member in a">
          <li>{{ member }}</li>
        </ul>
      </div>
  137. 7.3. 節點控制

  138. 7.3.1. 樣式 ng-style

  139. 可使用一個結構直接表示當前節點的樣式:

  140.   <div ng-style="{width: 100 + 'px', height: 100 + 'px', backgroundColor: 'red'}">
      </div>
  141. 一樣地,綁定一個變量的話,威力大了。

  142. 7.3.2. 類 ng-class

  143. 就是直接地設置當前節點的類,一樣,配合數據綁定做用就大了:

  144.   <div ng-controller="TestCtrl" ng-class="cls">
      </div>
  145. ng-class-even 和 ng-class-odd 是和 ng-repeat 配合使用的:

  146.   <ul ng-init="l=[1,2,3,4]">
        <li ng-class-odd="'odd'" ng-class-even="'even'" ng-repeat="m in l">{{ m }}</li>
      </ul>
  147. 注意裏面給的仍是表示式,別少了引號。

  148. 7.3.3. 顯示和隱藏 ng-show ng-hide ng-switch

  149. 前兩個是控制 display 的指令:

  150.   <div ng-show="true">1</div>
      <div ng-show="false">2</div>
      <div ng-hide="true">3</div>
      <div ng-hide="false">4</div>
  151. 後一個 ng-switch 是根據一個值來決定哪一個節點顯示,其它節點移除:

  152.   <div ng-init="a=2">
        <ul ng-switch on="a">
          <li ng-switch-when="1">1</li>
          <li ng-switch-when="2">2</li>
          <li ng-switch-default>other</li>
        </ul>
      </div>
  153. 7.3.4. 其它屬性控制

  154. ng-src 控制 src 屬性:

  155.   <img ng-src="{{ 'h' + 'ead.png' }}" />
  156. ng-href 控制 href 屬性:

  157.   <a ng-href="{{ '#' + '123' }}">here</a>
  158. 總的來講:

  159. ng-src src屬性

  160. ng-href href屬性

  161. ng-checked 選中狀態

  162. ng-selected 被選擇狀態

  163. ng-disabled 禁用狀態

  164. ng-multiple 多選狀態

  165. ng-readonly 只讀狀態

  166. 注意: 上面的這些只是單向綁定,即只是從數據到展現,不能副作用於數據。要雙向綁定,仍是要使用 ng-model 。

  167. 7.4. 事件綁定

  168. 事件綁定是模板指令中很好用的一部分。咱們能夠把相關事件的處理函數直接寫在 DOM 中,這樣作的最大好處就是能夠從 DOM 結構上看出業務處理的形式,你知道當你點擊這個節點時哪一個函數被執行了。

  169. ng-change

  170. ng-click

  171. ng-dblclick

  172. ng-mousedown

  173. ng-mouseenter

  174. ng-mouseleave

  175. ng-mousemove

  176. ng-mouseover

  177. ng-mouseup

  178. ng-submit

  179. 對於事件對象自己,在函數調用時能夠直接使用 $event 進行傳遞:

  180.   <p ng-click="click($event)">點擊</p>
      <p ng-click="click($event.target)">點擊</p>
  181. 7.5. 表單控件

  182. 表單控件類的模板指令,最大的做用是它預約義了須要綁定的數據的格式。這樣,就能夠對於既定的數據進行既定的處理。

  183. 7.5.1. form

  184. form 是核心的一個控件。 ng 對 form 這個標籤做了包裝。事實上, ng 本身的指令是叫 ng-form的,區別在於, form 標籤不能嵌套,而使用 ng-form 指令就能夠作嵌套的表單了。

  185. form 的行爲中依賴它裏面的各個輸入控制的狀態的,在這裏,咱們主要關心的是 form 本身的一些方法和屬性。從 ng 的角度來講, form 標籤,是一個模板指令,也建立了一個 FormController 的實例。這個實例就提供了相應的屬性和方法。同時,它裏面的控件也是一個 NgModelController 實例。

  186. 很重要的一點, form 的相關方法要生效,必須爲 form 標籤指定 name 和 ng-controller ,而且每一個控件都要綁定一個變量。 form 和控件的名字,便是 $scope 中的相關實例的引用變量名。

  187.   <form name="test_form" ng-controller="TestCtrl">
        <input type="text" name="a" required ng-model="a"  />
        <span ng-click="see()">{{ test_form.$valid }}</span>
      </form>
      
      var TestCtrl = function($scope){
      
        $scope.see = function(){
          console.log($scope.test_form);
          console.log($scope.test_form.a);
        }
      
      }
  188. 除去對象的方法與屬性, form 這個標籤自己有一些動態類可使用:

  189. ng-valid 當表單驗證經過時的設置

  190. ng-invalid 當表單驗證失敗時的設置

  191. ng-pristine 表單的未被動以前擁有

  192. ng-dirty 表單被動過以後擁有

  193. form 對象的屬性有:

  194. $pristine 表單是否未被動過

  195. $dirty 表單是否被動過

  196. $valid 表單是否驗證經過

  197. $invalid 表單是否驗證失敗

  198. $error 表單的驗證錯誤

  199. 其中的 $error 對象包含有全部字段的驗證信息,及對相關字段的 NgModelController 實例的引用。它的結構是一個對象, key 是失敗信息, required , minlength 之類的, value 是對應的字段實例列表。

  200. 注意,這裏的失敗信息是按序列取的一個。好比,若是一個字段既要求 required ,也要求minlength ,那麼當它爲空時, $error 中只有 required 的失敗信息。只輸入一個字符以後,required 條件知足了,纔可能有 minlength 這個失敗信息。

  201.   <form name="test_form" ng-controller="TestCtrl">
        <input type="text" name="a" required ng-model="a"  />
        <input type="text" name="b" required ng-model="b" ng-minlength="2" />
        <span ng-click="see()">{{ test_form.$error }}</span>
      </form>
      
      var TestCtrl = function($scope){
        $scope.see = function(){
          console.log($scope.test_form.$error);
        }
      }
  202. 7.5.2. input

  203. input 是數據的最主要入口。 ng 支持 HTML5 中的相關屬性,同時對舊瀏覽器也作了兼容性處理。最重要的, input 的規則定義,是所屬表單的相關行爲的參照(好比表單是否驗證成功)。

  204. input 控件的相關可用屬性爲:

  205. name 名字

  206. ng-model 綁定的數據

  207. required 是否必填

  208. ng-required 是否必填

  209. ng-minlength 最小長度

  210. ng-maxlength 最大長度

  211. ng-pattern 匹配模式

  212. ng-change 值變化時的回調

  213.   <form name="test_form" ng-controller="TestCtrl">
        <input type="text" name="a" ng-model="a" required ng-pattern="/abc/" />
        <span ng-click="see()">{{ test_form.$error }}</span>
      </form>
  214. input 控件,它還有一些擴展,這些擴展有些有本身的屬性:

  215. input type="number" 多了 number 錯誤類型,多了 max , min 屬性。

  216. input type="url" 多了 url 錯誤類型。

  217. input type="email" 多了 email 錯誤類型。

  218. 7.5.3. checkbox

  219. 它也算是 input 的擴展,不過,它沒有驗證相關的東西,只有選中與不選中兩個值:

  220.   <form name="test_form" ng-controller="TestCtrl">
        <input type="checkbox" name="a" ng-model="a" ng-true-value="AA" ng-false-value="BB" />
        <span>{{ a }}</span>
      </form>
      
      var TestCtrl = function($scope){
        $scope.a = 'AA';
      }
  221. 兩點:

  222. controller 要初始化變量值。

  223. controller 中的初始化值會關係到控件狀態(雙向綁定)。

  224. 7.5.4. radio

  225. 也是 input 的擴展。和 checkbox 同樣,但它只有一個值了:

  226.   <form name="test_form" ng-controller="TestCtrl">
        <input type="radio" name="a" ng-model="a" value="AA" />
        <input type="radio" name="a" ng-model="a" value="BB" />
        <span>{{ a }}</span>
      </form>
  227. 7.5.5. textarea

  228. 同 input 。

  229. 7.5.6. select

  230. 這是一個比較牛B的控件。它裏面的一個叫作 ng-options 的屬性用於數據呈現。

  231. 對於給定列表時的使用。

  232. 最簡單的使用方法, x for x in list :

  233.   <form name="test_form" ng-controller="TestCtrl" ng-init="o=[0,1,2,3]; a=o[1];">
        <select ng-model="a" ng-options="x for x in o" ng-change="show()">
          <option value="">能夠加這個空值</option>
        </select>
      </form>
      
      <script type="text/javascript">  var TestCtrl = function($scope){
        $scope.show = function(){
          console.log($scope.a);
        }
      }
      
      angular.bootstrap(document.documentElement);
      </script>
  234. 在 $scope 中, select 綁定的變量,其值和普通的 value 無關,能夠是一個對象:

  235.   <form name="test_form" ng-controller="TestCtrl"
            ng-init="o=[{name: 'AA'}, {name: 'BB'}]; a=o[1];">
        <select ng-model="a" ng-options="x.name for x in o" ng-change="show()">
        </select>
      </form>
  236. 顯示與值分別指定, x.v as x.name for x in o :

  237.   <form name="test_form" ng-controller="TestCtrl"
            ng-init="o=[{name: 'AA', v: '00'}, {name: 'BB', v: '11'}]; a=o[1].v;">
        <select ng-model="a" ng-options="x.v as x.name for x in o" ng-change="show()">
        </select>
      </form>
  238. 加入分組的, x.name group by x.g for x in o :

  239.   <form name="test_form" ng-controller="TestCtrl"
            ng-init="o=[{name: 'AA', g: '00'}, {name: 'BB', g: '11'}, {name: 'CC', g: '00'}]; a=o[1];">
        <select ng-model="a" ng-options="x.name group by x.g for x in o" ng-change="show()">
        </select>
      </form>
  240. 分組了還分別指定顯示與值的, x.v as x.name group by x.g for x in o :

  241.   <form name="test_form" ng-controller="TestCtrl" ng-init="o=[{name: 'AA', g: '00', v: '='}, {name: 'BB', g: '11', v: '+'}, {name: 'CC', g: '00', v: '!'}]; a=o[1].v;">
        <select ng-model="a" ng-options="x.v as x.name group by x.g for x in o" ng-change="show()">
        </select>
      </form>
  242. 若是參數是對象的話,基本也是同樣的,只是把遍歷的對象改爲 (key, value) :

  243.   <form name="test_form" ng-controller="TestCtrl" ng-init="o={a: 0, b: 1}; a=o.a;">
        <select ng-model="a" ng-options="k for (k, v) in o" ng-change="show()">
        </select>
      </form>
      
      <form name="test_form" ng-controller="TestCtrl"
            ng-init="o={a: {name: 'AA', v: '00'}, b: {name: 'BB', v: '11'}}; a=o.a.v;">
        <select ng-model="a" ng-options="v.v as v.name for (k, v) in o" ng-change="show()">
        </select>
      </form>
      
      <form name="test_form" ng-controller="TestCtrl"
            ng-init="o={a: {name: 'AA', v: '00', g: '=='}, b: {name: 'BB', v: '11', g: '=='}}; a=o.a;">
        <select ng-model="a" ng-options="v.name group by v.g for (k, v) in o" ng-change="show()">
        </select>
      </form>
      
      <form name="test_form" ng-controller="TestCtrl"
            ng-init="o={a: {name: 'AA', v: '00', g: '=='}, b: {name: 'BB', v: '11', g: '=='}}; a=o.a.v;">
        <select ng-model="a" ng-options="v.v as v.name group by v.g for (k, v) in o" ng-change="show()">
        </select>
      </form>
  244. 8. 模板中的過濾器

  245. 這裏說的過濾器,是用於對數據的格式化,或者篩選的函數。它們能夠直接在模板中經過一種語法使用。對於經常使用功能來講,是很方便的一種機制。

  246. 多個過濾器之間能夠直接連續使用。

  247. 8.1. 排序 orderBy

  248. orderBy 是一個排序用的過濾器標籤。它能夠像 sort 函數那樣支持一個排序函數,也能夠簡單地指定一個屬性名進行操做:

  249.   <div ng-controller="TestCtrl">
        {{ data | orderBy: 'age' }} <br />
        {{ data | orderBy: '-age' }} <br />
        {{ data | orderBy: '-age' | limitTo: 2 }} <br />
        {{ data | orderBy: ['-age', 'name'] }} <br />
      </div>
      
      
      <script type="text/javascript">  var TestCtrl = function($scope){
        $scope.data = [
          {name: 'B', age: 4},  
          {name: 'A', age: 1},  
          {name: 'D', age: 3},  
          {name: 'C', age: 3},  
        ];
      }
      
      angular.bootstrap(document.documentElement);
      </script>
  250. 8.2. 過濾列表 filter

  251. filter 是一個過濾內容的標籤。

  252. 若是參數是一個字符串,則列表成員中的任意屬性值中有這個字符串,即爲知足條件(忽略大小寫):

  253.   <div ng-controller="TestCtrl">
        {{ data | filter: 'b' }} <br />
        {{ data | filter: '!B' }} <br />
      </div>
      
      
      <script type="text/javascript">  var TestCtrl = function($scope){
        $scope.data = [
          {name: 'B', age: 4},  
          {name: 'A', age: 1},  
          {name: 'D', age: 3},  
          {name: 'C', age: 3},  
        ];
      }
      
      angular.bootstrap(document.documentElement);
      </script>
  254. 可使用對象,來指定屬性名, $ 表示任意屬性:

  255.   {{ data | filter: {name: 'A'} }} <br />
      {{ data | filter: {$: '3'} }} <br />
      {{ data | filter: {$: '!3'} }} <br />
  256. 自定義的過濾函數也支持:

  257.   <div ng-controller="TestCtrl">
        {{ data | filter: f }} <br />
      </div>
      
      
      <script type="text/javascript">  var TestCtrl = function($scope){
        $scope.data = [
          {name: 'B', age: 4},  
          {name: 'A', age: 1},  
          {name: 'D', age: 3},  
          {name: 'C', age: 3},  
        ];
      
        $scope.f = function(e){      return e.age > 2;
        }
      }
      
      angular.bootstrap(document.documentElement);
      </script>
  258. 8.3. 其它

  259. 時間戳格式化 date :

  260.   <div ng-controller="TestCtrl">
      {{ a | date: 'yyyy-MM-dd HH:mm:ss' }}
      </div>
      
      <script type="text/javascript">  var TestCtrl = function($scope){
        $scope.a = ((new Date().valueOf()));
      }
      
      angular.bootstrap(document.documentElement);
      </script>
  261. 列表截取 limitTo ,支持正負數:

  262.   {{ [1,2,3,4,5] | limitTo: 2 }}
      {{ [1,2,3,4,5] | limitTo: -3 }}
  263. 大小寫 lowercase , uppercase :

  264.   {{ 'abc' | uppercase }}
      {{ 'Abc' | lowercase }}
  265. 8.4. 例子:表頭排序

  266.  1   <div ng-controller="TestCtrl"> 2     <table> 3       <tr> 4         <th ng-click="f='name'; rev=!rev">名字</th> 5         <th ng-click="f='age'; rev=!rev">年齡</th> 6       </tr> 7    8       <tr ng-repeat="o in data | orderBy: f : rev"> 9         <td>{{ o.name }}</td>10         <td>{{ o.age }}</td>11       </tr>12     </table>13   </div>14   15   <script type="text/javascript">16   var TestCtrl = function($scope){17     $scope.data = [18       {name: 'B', age: 4},  
    19       {name: 'A', age: 1},  
    20       {name: 'D', age: 3},  
    21       {name: 'C', age: 3},  
    22     ];23   }24   25   angular.bootstrap(document.documentElement);26   </script>
  267. 8.5. 例子:搜索

  268.   <div ng-controller="TestCtrl" ng-init="s=data[0].name; q=''">
        <div>
          <span>查找:</span> <input type="text" ng-model="q" />
        </div>
        <select ng-multiple="true" ng-model="s"
                ng-options="o.name as o.name + '(' + o.age + ')' for o in data | filter: {name: q} | orderBy: ['age', 'name'] ">
        </select>
      </div>
      
      <script type="text/javascript">  var TestCtrl = function($scope){
        $scope.data = [
          {name: 'B', age: 4},  
          {name: 'A', age: 1},  
          {name: 'D', age: 3},  
          {name: 'C', age: 3},  
        ];
      }
      
      angular.bootstrap(document.documentElement);
      </script>
  269. 9. 錨點路由

  270. 準確地說,這應該叫對 hashchange 事件的處理吧。

  271. 就是指 URL 中的錨點部分發生變化時,觸發預先定義的業務邏輯。好比如今是 /test#/x ,錨點部分的值爲 # 後的 /x ,它就對應了一組處理邏輯。當這部分變化時,好比變成了 /test#/t ,這時頁面是不會刷新的,可是它能夠觸發另一組處理邏輯,來作一些事,也可讓頁面發生變化。

  272. 這種機制對於複雜的單頁面來講,無疑是一種強大的業務切分手段。就算不是複雜的單頁面應用,在普通頁面上善用這種機制,也可讓業務邏輯更容易控制。

  273. ng 提供了完善的錨點路由功能,雖然目前我以爲至關重要的一個功能還有待完善(後面會說),但目前這功能的幾部份內容,已經讓我思考了不少種可能性了。

  274. ng 中的錨點路由功能是由幾部分 API 共同完成的一整套方案。這其中包括了路由定義,參數定義,業務處理等。

  275. 9.1. 路由定義

  276. 要使用錨點路由功能,須要在先定義它。目前,對於定義的方法,我我的只發如今「初始化」階段能夠經過 $routeProvider 這個服務來定義。

  277. 在定義一個 app 時能夠定義錨點路由:

  278.   <html ng-app="ngView">
        ... ...
      
      <div ng-view></div>
      
      <script type="text/javascript">
      
      angular.module('ngView', [],
        function($routeProvider){
          $routeProvider.when('/test',
            {
              template: 'test',
            }
          );
        }
      );
      
      </script>
  279. 首先看 ng-view 這個 directive ,它是一個標記「錨點做用區」的指令。目前頁面上只能有一個「錨點做用區」。有人已經提了,「多個可命名」的錨點做用區的代碼到官方,可是目前官方尚未接受合併,我以爲多個做用區這個功能是很重要的,但願下個發佈版中能有。

  280. 錨點做用區的功能,就是讓錨點路由定義時的那些模板, controller 等,它們產生的 HTML 代碼放在做用區內。

  281. 好比上面的代碼,當你剛打開頁面時,頁面是空白的。你手動訪問 /#/test 就能夠看到頁面上出現了 'test' 的字樣。

  282. 在 angular.bootstrap() 時也能夠定義:

  283.   angular.bootstrap(document.documentElement, [    function($routeProvider){
          $routeProvider.when('/test',
            {
              template: 'test'
            }
          );
        }
      ]);
  284. 9.2. 參數定義

  285. 在做路由定義時,能夠匹配一個規則,規則中能夠定義路徑中的某些部分做爲參數之用,而後使用$routeParams 服務獲取到指定參數。好比 /#/book/test 中, test 做爲參數傳入到 controller 中:

  286.   <div ng-view></div>  
      
      <script type="text/javascript">
      
      angular.module('ngView', [],    function($routeProvider){
          $routeProvider.when('/book/:title',
            {
              template: '{{ title }}',
              controller: function($scope, $routeParams){
                $scope.title = $routeParams.title;
              }
            }
          );
        }
      );  
      </script>
  287. 訪問: /#/book/test

  288. 不須要預約義模式,也能夠像普通 GET 請求那樣獲取到相關參數:

  289.   angular.module('ngView', [],    function($routeProvider){
          $routeProvider.when('/book',
            {
              template: '{{ title }}',
              controller: function($scope, $routeParams){
                $scope.title = $routeParams.title;
              }
            }
          );
        }
      );
  290. 訪問: /#/book?title=test

  291. 9.3. 業務處理

  292. 簡單來講,當一個錨點路由定義被匹配時,會根據模板生成一個 $scope ,同時相應的一個 controller 就會被觸發。最後模板的結果會被填充到 ng-view 中去。

  293. 從上面的例子中能夠看到,最直接的方式,咱們能夠在模板中雙向綁定數據,而數據的來源,在 controller 中控制。在 controller 中,又可使用到像 $scope , $routeParams 這些服務。

  294. 這裏先提一下另一種與錨點路由相關的服務, $route 。這個服務裏錨點路由在定義時,及匹配過程當中的信息。好比咱們搞怪一下:

  295.   angular.module('ngView', [],    function($routeProvider){
          $routeProvider.when('/a',
            {
              template: '{{ title }}',
              controller: function($scope){
                $scope.title = 'a';
              }
            }
          );
      
          $routeProvider.when('/b',
            {
              template: '{{ title }}',
              controller: function($scope, $route){
                console.log($route);
                $route.routes['/a'].controller($scope);
              }
            }
          );
        }
      );
  296. 回到錨點定義的業務處理中來。咱們能夠以字符串形式寫模板,也能夠直接引用外部文件做爲模板:

  297.   angular.module('ngView', [],
        function($routeProvider){
          $routeProvider.when('/test',
            {
              templateUrl: 'tpl.html',
              controller: function($scope){
                $scope.title = 'a';
              }
            }
          );
        }
      );
  298. tpl.html 中的內容是:

  299.   {{ title }}
  300. 這樣的話,模板能夠預約義,也能夠很複雜了。

  301. 如今暫時忘了模板吧,由於前面提到的,當前 ng-view 不能有多個的限制,模板的渲染機制侷限性仍是很大的。不過,反正會觸發一個 controller ,那麼在函數當中咱們能夠儘可能地幹本身喜歡的事:

  302.   angular.module('ngView', [],
        function($routeProvider){
          $routeProvider.when('/test',
            {
              template: '{{}}',
              controller: function(){
                $('div').first().html('<b>OK</b>');
              }
            }
          );
        }
      );
  303. 那個空的 template 不能省,不然 controller 不會被觸發。

  304. 10. 定義模板變量標識標籤

  305. 因爲下面涉及動態內容,因此我打算起一個後端服務來作。可是我發現我使用的 Tornado 框架的模板系統,與 ng 的模板系統,都是使用 {{ }} 這對符號來定義模板表達式的,這太悲劇了,不過幸虧 ng 已經提供了修改方法:

  306.   angular.bootstrap(document.documentElement,
        [function($interpolateProvider){
          $interpolateProvider.startSymbol('[[');
          $interpolateProvider.endSymbol(']]');
        }]);
  307. 使用 $interpolateProvider 服務便可。

  308. 11. AJAX

  309. ng 提供了基本的 AJAX 封裝,你直接面對 promise 對象,使用起來仍是很方便的。

  310. 11.1. HTTP請求

  311. 基本的操做由 $http 服務提供。它的使用很簡單,提供一些描述請求的參數,請求就出去了,而後返回一個擴充了 success 方法和 error 方法的 promise 對象(下節介紹),你能夠在這個對象中添加須要的回調函數。

  312.   var TestCtrl = function($scope, $http){    var p = $http({
          method: 'GET',
          url: '/json'
        });
        p.success(function(response, status, headers, config){
            $scope.name = response.name;
        });
      }
  313. $http 接受的配置項有:

  314. method 方法

  315. url 路徑

  316. params GET請求的參數

  317. data post請求的參數

  318. headers 頭

  319. transformRequest 請求預處理函數

  320. transformResponse 響應預處理函數

  321. cache 緩存

  322. timeout 超時毫秒,超時的請求會被取消

  323. withCredentials 跨域安全策略的一個東西

  324. 其中的 transformRequest 和 transformResponse 及 headers 已經有定義的,若是自定義則會覆蓋默認定義:

  325.  1   var $config = this.defaults = { 2     // transform incoming response data 3     transformResponse: [function(data) { 4       if (isString(data)) { 5         // strip json vulnerability protection prefix 6         data = data.replace(PROTECTION_PREFIX, ''); 7         if (JSON_START.test(data) && JSON_END.test(data)) 8           data = fromJson(data, true); 9       }10       return data;11     }],12   13     // transform outgoing request data14     transformRequest: [function(d) {15       return isObject(d) && !isFile(d) ? toJson(d) : d;16     }],17   18     // default headers19     headers: {20       common: {21         'Accept': 'application/json, text/plain, */*',22         'X-Requested-With': 'XMLHttpRequest'23       },24       post: {'Content-Type': 'application/json;charset=utf-8'},25       put:  {'Content-Type': 'application/json;charset=utf-8'}26     }27   };
  326. 注意它默認的 POST 方法出去的 Content-Type

  327. 對於幾個標準的 HTTP 方法,有對應的 shortcut :

  328. $http.delete(url, config)

  329. $http.get(url, config)

  330. $http.head(url, config)

  331. $http.jsonp(url, config)

  332. $http.post(url, data, config)

  333. $http.put(url, data, config)

  334. 注意其中的 JSONP 方法,在實現上會在頁面中添加一個 script 標籤,而後放出一個 GET 請求。你本身定義的,匿名回調函數,會被 ng 自已給一個全局變量。在定義請求,做爲 GET 參數,你可使用 JSON_CALLBACK 這個字符串來暫時代替回調函數名,以後 ng 會爲你替換成真正的函數名:

  335.   var p = $http({
        method: 'JSONP',
        url: '/json',
        params: {callback: 'JSON_CALLBACK'}
      });
      p.success(function(response, status, headers, config){
          console.log(response);
          $scope.name = response.name;
      });
  336. $http 有兩個屬性:

  337. defaults 請求的全局配置

  338. pendingRequests 當前的請求隊列狀態

  339.   $http.defaults.transformRequest = function(data){console.log('here'); return data;}
      console.log($http.pendingRequests);
  340. 11.2. 廣義回調管理

  341. 和其它框架同樣, ng 提供了廣義的異步回調管理的機制。 $http 服務是在其之上封裝出來的。這個機制就是 ng 的 $q 服務。

  342. 不過 ng 的這套機制總的來講實現得比較簡單,按官方的說法,夠用了。

  343. 使用的方法,基本上是:

  344. 經過 $q 服務獲得一個 deferred 實例

  345. 經過 deferred 實例的 promise 屬性獲得一個 promise 對象

  346. promise 對象負責定義回調函數

  347. deferred 實例負責觸發回調

  348.   var TestCtrl = function($q){    var defer = $q.defer();    var promise = defer.promise;
        promise.then(function(data){console.log('ok, ' + data)},                 function(data){console.log('error, ' + data)});    //defer.reject('xx');
        defer.resolve('xx');
      }
  349. 瞭解了上面的東西,再分別看 $q , deferred , promise 這三個東西。

  350. 11.2.1. $q

  351. $q 有四個方法:

  352. $q.all() 合併多個 promise ,獲得一個新的 promise

  353. $q.defer() 返回一個 deferred 對象

  354. $q.reject() 包裝一個錯誤,以使回調鏈能正確處理下去

  355. $q.when() 返回一個 promise 對象

  356. $q.all() 方法適用於併發場景很合適:

  357.   var TestCtrl = function($q, $http){    var p = $http.get('/json', {params: {a: 1}});    var p2 = $http.get('/json', {params: {a: 2}});    var all = $q.all([p, p2]);
        p.success(function(res){console.log('here')});
        all.then(function(res){console.log(res[0])});
      }
  358. $q.reject() 方法是在你捕捉異常以後,又要把這個異常在回調鏈中傳下去時使用:

  359. 要理解這東西,先看看 promise 的鏈式回調是如何運做的,看下面兩段代碼的區別:

  360.   var defer = $q.defer();  var p = defer.promise;
      p.then(    function(data){return 'xxx'}
      );
      p.then(    function(data){console.log(data)}
      );
      defer.resolve('123');
  361.   var defer = $q.defer();  var p = defer.promise;  var p2 = p.then(    function(data){return 'xxx'}
      );
      p2.then(    function(data){console.log(data)}
      );
      defer.resolve('123');
  362. 從模型上看,前者是「併發」,後者纔是「鏈式」。

  363. 而 $q.reject() 的做用就是觸發後鏈的 error 回調:

  364.   var defer = $q.defer();  var p = defer.promise;
      p.then(    function(data){return data},    function(data){return $q.reject(data)}
      ).
      then(    function(data){console.log('ok, ' + data)},    function(data){console.log('error, ' + data)}
      )
      defer.reject('123');
  365. 最後的 $q.when() 是把數據封裝成 promise 對象:

  366.   var p = $q.when(0, function(data){return data},                     function(data){return data});
      p.then(    function(data){console.log('ok, ' + data)},    function(data){console.log('error, ' + data)}
      );
  367. 11.2.2. deferred

  368. deferred 對象有兩個方法一個屬性。

  369. promise 屬性就是返回一個 promise 對象的。

  370. resolve() 成功回調

  371. reject() 失敗回調

  372.   var defer = $q.defer();  var promise = defer.promise;
      promise.then(function(data){console.log('ok, ' + data)},               function(data){console.log('error, ' + data)});  //defer.reject('xx');
      defer.resolve('xx');
  373. 11.2.3. promise

  374. promise 對象只有 then() 一個方法,註冊成功回調函數和失敗回調函數,再返回一個 promise 對象,以用於鏈式調用。

  375. 12. 工具函數

  376. 12.1. 上下文綁定

  377. angular.bind 是用來進行上下文綁定,參數動態綁定的工具函數。

  378.   var f = angular.bind({a: 'xx'},
        function(){
          console.log(this.a);
        }
      );
      f();
  379. 參數動態綁定:

  380.   var f = function(x){console.log(x)}
      angular.bind({}, f, 'x')();
  381. 12.2. 對象處理

  382. 對象複製: angular.copy()

  383.   var a = {'x': '123'};
      var b = angular.copy(a);
      a.x = '456';
      console.log(b);
  384. 對象聚合: angular.extend()

  385.   var a = {'x': '123'};
      var b = {'xx': '456'};
      angular.extend(b, a);
      console.log(b);
  386. 空函數: angular.noop()

  387. 大小寫轉換: angular.lowercase() 和 angular.uppercase()

  388. JSON轉換: angular.fromJson() 和 angular.toJson()

  389. 遍歷: angular.forEach() ,支持列表和對象:

  390.   var l = {a: '1', b: '2'};
      angular.forEach(l, function(v, k){console.log(k + ': ' + v)});
      
      var l = ['a', 'b', 'c'];
      angular.forEach(l, function(v, i, o){console.log(v)});
      
      var context = {'t': 'xx'};
      angular.forEach(l, function(v, i, o){console.log(this.t)}, context);
  391. 12.3. 類型斷定

  392. angular.isArray

  393. angular.isDate

  394. angular.isDefined

  395. angular.isElement

  396. angular.isFunction

  397. angular.isNumber

  398. angular.isObject

  399. angular.isString

  400. angular.isUndefined

  401. 13. 其它服務

  402. 13.1. 日誌

  403. ng 提供 $log 這個服務用於向終端輸出相關信息:

  404. error()

  405. info()

  406. log()

  407. warn()

  408.   var TestCtrl = function($log){
        $log.error('error');
        $log.info('info');
        $log.log('log');
        $log.warn('warn');
      }
  409. 13.2. 緩存

  410. ng 提供了一個簡單封裝了緩存機制 $cacheFactory ,能夠用來做爲數據容器:

  411.   var TestCtrl = function($scope, $cacheFactory){
        $scope.cache = $cacheFactory('s_' + $scope.$id, {capacity: 3});
      
        $scope.show = function(){
          console.log($scope.cache.get('a'));
          console.log($scope.cache.info());
        }
      
        $scope.set = function(){
          $scope.cache.put((new Date()).valueOf(), 'ok');
        }
      }
  412. 調用時,第一個參數是 id ,第二個參數是配置項,目前支持 capacity 參數,用以設置緩存能容留的最大條目數。超過這個個數,則自動清除較舊的條目。

  413. 緩存實例的方法:

  414. info() 獲取 id , size 信息

  415. put(k, v) 設置新條目

  416. get(k) 獲取條目

  417. remove(k) 刪除條目

  418. removeAll() 刪除全部條目

  419. destroy() 刪除對本實例的引用

  420. $http 的調用當中,有一個 cache 參數,值爲 true 時爲自動維護的緩存。值也能夠設置爲一個 cache 實例。

  421. 13.3. 計時器

  422. $timeout 服務是 ng 對 window.setTimeout() 的封裝,它使用 promise 統一了計時器的回調行爲:

  423.   var TestCtrl = function($timeout){
        var p = $timeout(function(){console.log('haha')}, 5000);
        p.then(function(){console.log('x')});    //$timeout.cancel(p);
      }
  424. 使用 $timeout.cancel() 能夠取消計時器。

  425. 13.4. 表達式函數化

  426. $parse 這個服務,爲 js 提供了相似於 Python 中 @property 的能力:

  427.   var TestCtrl = function($scope, $parse){
        $scope.get_name = $parse('name');
        $scope.show = function(){console.log($scope.get_name($scope))}
        $scope.set = function(){$scope.name = '123'}
      }
  428. $parse 返回一個函數,調用這個函數時,能夠傳兩個參數,第一個做用域,第二個是變量集,後者經常使用於覆蓋前者的變量:

  429.   var get_name = $parse('name');  var r = get_name({name: 'xx'}, {name: 'abc'});
      console.log(r);
  430. $parse 返回的函數,也提供了相應的 assign 功能,能夠爲表達式賦值(若是能夠的話):

  431.   var get_name = $parse('name');  var set_name = get_name.assign;  var r = get_name({name: 'xx'}, {name: 'abc'});
      console.log(r);  
      var s = {}
      set_name(s, '123');  var r = get_name(s);
      console.log(r);
  432. 13.5. 模板單獨使用

  433. ng 中的模板是很重要,也很強大的一個機制,天然少不了單獨運用它的方法。不過,即便是單獨使用,也是和 DOM 緊密相關的程度:

  434. 定義時必須是有 HTML 標籤包裹的,這樣才能建立 DOM 節點

  435. 渲染時必須傳入 $scope

  436. 以後使用 $compile 就能夠獲得一個渲染好的節點對象了。固然, $compile 還要作其它一些工做,指令處理什麼的。

  437.   var TestCtrl = function($scope, $element,$compile){
        $scope.a = '123';
        $scope.set = function(){      var tpl = $compile('<p>hello {{ a }}</p>');      var e = tpl($scope);
          $element.append(e);
        }
      }
  438. 14. 自定義模塊和服務

  439. 14.1. 模塊和服務的概念與關係

  440. 總的來講,模塊是組織業務的一個框框,在一個模塊當中定義多個服務。當你引入了一個模塊的時候,就可使用這個模塊提供的一種或多種服務了。

  441. 好比 AngularJS 自己的一個默認模塊叫作 ng ,它提供了 $http , $q 等等服務。

  442. 服務只是模塊提供的多種機制中的一種,其它的還有命令( directive ),過濾器( filter ),及其它配置信息。

  443. 而後在額外的 js 文件中有一個附加的模塊叫作 ngResource , 它提供了一個 $resource 服務。

  444. 定義時,咱們能夠在已有的模塊中新定義一個服務,也能夠先新定義一個模塊,而後在新模塊中定義新服務。

  445. 使用時,模塊是須要顯式地的聲明依賴(引入)關係的,而服務則可讓 ng 自動地作注入,而後直接使用。

  446. 14.2. 定義模塊

  447. 定義模塊的方法是使用 angular.module 。調用時聲明瞭對其它模塊的依賴,並定義了「初始化」函數。

  448.   var my_module = angular.module('MyModule', [], function(){
          console.log('here');
      });
  449. 這段代碼定義了一個叫作 MyModule 的模塊, my_module 這個引用能夠在接下來作其它的一些事,好比定義服務。

  450. 14.3. 定義服務

  451. 服務自己是一個任意的對象。可是 ng 提供服務的過程涉及它的依賴注入機制。在這裏呢,就要先介紹一下叫 provider 的東西。

  452. 簡單來講, provider 是被「注入控制器」使用的一個對象,注入機制經過調用一個 provider 的 $get()方法,把獲得的東西做爲參數進行相關調用(好比把獲得的服務做爲一個 Controller 的參數)。

  453. 在這裏「服務」的概念就比較不明確,對使用而言,服務僅指 $get() 方法返回的東西,可是在總體機制上,服務又要指提供了 $get() 方法的整個對象。

  454.   //這是一個provider
      var pp = function(){    this.$get = function(){      return {'haha': '123'};
        }
      }  
      //我在模塊的初始化過程中, 定義了一個叫 PP 的服務
      var app = angular.module('Demo', [], function($provide){
        $provide.provider('PP', pp);
      });  
      //PP服務實際上就是 pp 這個 provider 的 $get() 方法返回的東西
      app.controller('TestCtrl',    function($scope, PP){
          console.log(PP);
        }
      );
  455. 上面的代碼是一種定義服務的方法,固然, ng 還有相關的 shortcut, ng 總有不少 shortcut 。

  456. 第一個是 factory 方法,由 $provide 提供, module 的 factory 是一個引用,做用同樣。這個方法直接把一個函數當成是一個對象的 $get() 方法,這樣你就不用顯式地定義一個 provider 了:

  457.   var app = angular.module('Demo', [], function($provide){
        $provide.factory('PP', function(){      return {'hello': '123'};
        });
      });
      app.controller('TestCtrl', function($scope, PP){ console.log(PP) });
  458. 在 module 中使用:

  459.   var app = angular.module('Demo', [], function(){ });
      app.factory('PP', function(){return {'abc': '123'}});
      app.controller('TestCtrl', function($scope, PP){ console.log(PP) });
  460. 第二個是 service 方法,也是由 $provide 提供, module 中有對它的同名引用。 service 和factory 的區別在於,前者是要求提供一個「構造方法」,後者是要求提供 $get() 方法。意思就是,前者必定是獲得一個 object ,後者能夠是一個數字或字符串。它們的關係大概是:

  461.   var app = angular.module('Demo', [], function(){ });
      app.service = function(name, constructor){
        app.factory(name, function(){      return (new constructor());
        });
      }
  462. 這裏插一句,js 中 new 的做用,以 new a() 爲例,過程至關於:

  463. 建立一個空對象 obj

  464. 把 obj 綁定到 a 函數的上下文當中(即 a 中的 this 如今指向 obj )

  465. 執行 a 函數

  466. 返回 obj

  467. service 方法的使用就很簡單了:

  468.   var app = angular.module('Demo', [], function(){ });
      app.service('PP', function(){    this.abc = '123';
      });
      app.controller('TestCtrl', function($scope, PP){ console.log(PP) });
  469. 14.4. 引入模塊並使用服務

  470. 結合上面的「定義模塊」和「定義服務」,咱們能夠方便地組織本身的額外代碼:

  471.   angular.module('MyModule', [], function($provide){
        $provide.factory('S1', function(){      return 'I am S1';
        });
        $provide.factory('S2', function(){      return {see: function(){return 'I am S2'}}
        });
      });  
      var app = angular.module('Demo', ['MyModule'], angular.noop);
      app.controller('TestCtrl', function($scope, S1, S2){
        console.log(S1)
        console.log(S2.see())
      });
  472. 15. 附加模塊 ngResource

  473. 15.1. 使用引入與總體概念

  474. ngResource 這個是 ng 官方提供的一個附加模塊。附加的意思就是,若是你打算用它,那麼你須要引入一人單獨的 js 文件,而後在聲明「根模塊」時註明依賴的 ngResource 模塊,接着就可使用它提供的 $resource 服務了。完整的過程形如:

  475.   <!DOCTYPE html>
      <html ng-app="Demo">
      <head>
      <meta charset="utf-8" />
      <title>AngularJS</title>
      <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"></script>
      <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular-resource.js"></script>
      </head>
      <body>
      
        <div ng-controller="TestCtrl"></div>
      
      
      <script type="text/javascript" charset="utf-8">  
      var app = angular.module('Demo', ['ngResource'], angular.noop);
      app.controller('TestCtrl', function($scope, $resource){
        console.log($resource);
      });
      
      </script>
      </body>
      </html>
  476. $resource 服務,總體上來講,比較像是使用相似 ORM 的方式來包裝了 AJAX 調用。區別就是 ORM 是操做數據庫,即拼出 SQL 語句以後,做 execute 方法調用。而 $resource 的方式是構造出 AJAX 請求,而後發出請求。同時,AJAX 請求是須要回調處理的,這方面, $resource 的機制可使你在一些時候省掉回調處理,固然,是否做回調處理在於業務情形及容錯需求了。

  477. 使用上 $resource 分紅了「類」與「實例」這兩個層面。通常地,類的方法調用就是直觀的調用形式,一般會返回一個對象,這個對象即爲「實例」。

  478. 「實例」貫穿整個服務的使用過程。「實例」的數據是填充方式,即由於異步關係,回調函數沒有執行時,實例已經存在,只是可能它尚未相關數據,回調執行以後,相關數據被填充到實例對象當中。實例的方法通常就是在類方法名前加一個 $ ,調用上,根據定義,實例數據可能會作一些自動的參數填充,這點是區別實例與類的調用上的不一樣。

  479. 好吧,上面這些話可能須要在看了接下來的內容以後再回過來理解。

  480. 15.2. 基本定義

  481. 就像使用 ORM 通常要先定義 Model 同樣,使用 $resource 須要先定義「資源」,也就是先定義一些 HTTP 請求。

  482. 在業務場景上,咱們假設爲,咱們須要操做「書」這個實體,包括建立create,獲取詳情read,修改update,刪除delete,批量獲取multi,共五個操做方法。實體屬性有:惟一標識id,標題title,做者author。

  483. 咱們把這些操做定義成 $resource 的資源:

  484.   var app = angular.module('Demo', ['ngResource'], angular.noop);
      app.controller('BookCtrl', function($scope, $resource){    var actions = {
          create: {method: 'POST', params: {_method: 'create'}},
          read: {method: 'POST', params: {_method: 'read'}},
          update: {method: 'POST', params: {_method: 'update'}},      delete: {method: 'POST', params: {_method: 'delete'}},
          multi: {method: 'POST', params: {_method: 'multi'}}
        }    var Book = $resource('/book', {}, actions);
      });
  485. 定義是使用使用 $resource 這個函數就能夠了,它接受三個參數:

  486. url

  487. 默認的params(這裏的 params 便是 GET 請求的參數,POST 的參數單獨叫作「postData」)

  488. 方法映射

  489. 方法映射是以方法名爲 key ,以一個對象爲 value ,這個 value 能夠有三個成員:

  490. method, 請求方法,'GET', 'POST', 'PUT', 'DELETE' 這些

  491. params, 默認的 GET 參數

  492. isArray, 返回的數據是否是一個列表

  493. 15.3. 基本使用

  494. 在定義了資源以後,咱們看若是使用這些資源,發出請求:

  495.   var book = Book.read({id: '123'}, function(response){
        console.log(response);
      });
  496. 這裏咱們進行 Book 的「類」方法調用。在方法的使用上,根據官方文檔:

  497.   HTTP GET "class" actions: Resource.action([parameters], [success], [error])
      non-GET "class" actions: Resource.action([parameters], postData, [success], [error])
      non-GET instance actions: instance.$action([parameters], [success], [error])
  498. 咱們這裏是第二種形式,即類方法的非 GET 請求。咱們給的參數會做爲 postData 傳遞。若是咱們須要 GET 參數,而且還須要一個錯誤回調,那麼:

  499.   var book = Book.read({get: 'haha'}, {id: '123'},    function(response){
          console.log(response);
        },    function(error){
          console.log(error);
        }
      );
  500. 調用以後,咱們會當即獲得的 book ,它是 Book 類的一個實例。這裏所謂的實例,實際上就是先把全部的 action 加一個 $ 前綴放到一個空對象裏,而後把發出的參數填充進去。等請求返回了,把除 action 之外的成員刪除掉,再把請求返回的數據填充到這個對象當中。因此,若是咱們這樣:

  501.   var book = Book.read({id: '123'}, function(response){
        console.log(book);
      });
      console.log(book)
  502. 就能看到 book 實例的變化過程了。

  503. 如今咱們獲得一個真實的實例,看一下實例的調用過程:

  504.   //響應的數據是 {result: 0, msg: '', obj: {id: 'xxx'}}
      var book = Book.create({title: '測試標題', author: '測試做者'}, function(response){
        console.log(book);
      });
  505. 能夠看到,在請求回調以後, book 這個實例的成員已經被響應內容填充了。可是這裏有一個問題,咱們返回的數據,並不適合一個 book 實例。格式先不說,它把 title 和 author 這些信息都丟了(由於響應只返回了 id )。

  506. 若是僅僅是格式問題,咱們能夠經過配置 $http 服務來解決( AJAX 請求都要使用 $http 服務的):

  507.   $http.defaults.transformResponse = function(data){return angular.fromJson(data).obj};
  508. 固然,咱們也能夠本身來解決一下丟信息的問題:

  509.   var p = {title: '測試標題', author: '測試做者'};  var book = Book.create(p, function(response){
        angular.extend(book, p);
        console.log(book);
      });
  510. 不過,始終會有一些不方便了。比較正統的方式應該是調節服務器端的響應,讓服務器端也具備和前端同樣的實例概念,返回的是完整的實例信息。即便這樣,你也還要考慮格式的事。

  511. 如今咱們獲得了一個真實的 book 實例了,帶有 id 信息。咱們嘗試一下實例的方法調用,先回過去頭看一下那三種調用形式,對於實例只有第三種形式:

  512.   non-GET instance actions: instance.$action([parameters], [success], [error])
  513. 首先解決一個疑問,若是一個實例是進行一個 GET 的調用會怎麼樣?沒有任何問題,這固然沒有任何問題的,形式和上面同樣。

  514. 如何實例是作 POST 請求的話,從形式上看,咱們沒法控制請求的 postData ?是的,全部的 POST 請求,其 postData 都會被實例數據自動填充,形式上咱們只能控制 params 。

  515. 因此,若是是在作修改調用的話:

  516.   book.$update({title: '新標題', author: '測試做者'}, function(response){
        console.log(book);
      });
  517. 這樣是沒有意義的而且錯誤的。由於要修改的數據只是做爲 GET 參數傳遞了,而 postData 傳遞的數據就是當前實例的數據,並無任何修改。

  518. 正確的作法:

  519.   book.title = '新標題'
      book.$update(function(response){
        console.log(book);
      });
  520. 顯然,這種狀況下,回調均可以省了:

  521.   book.title = '新標題'
      book.$update();
  522. 15.4. 定義和使用時的佔位量

  523. 兩方面。一是在定義時,在其 URL 中可使用變量引用的形式(類型於定義錨點路由時那樣)。第二時定義默認 params ,即 GET 參數時,能夠定義爲引用 postData 中的某變量。好比咱們這樣改一下:

  524.   var Book = $resource('/book/:id', {}, actions);  var book = Book.read({id: '123'}, {}, function(response){
        console.log(response);
      });
  525. 在 URL 中有一個 :id ,表示對 params 中 id 這個變量的引用。由於 read 是一個 POST 請求,根據調用形式,第一個參數是 params ,第二個參數是 postData 。這樣的調用結果就是,咱們會發一個 POST 請求到以下地址, postData 爲空:

  526.   /book/123?_method=read
  527. 再看默認的 params 中引用 postData 變量的形式:

  528.   var Book = $resource('/book', {id: '@id'}, actions);  var book = Book.read({title: 'xx'}, {id: '123'}, function(response){
        console.log(response);
      });
  529. 這樣會出一個 POST 請求, postData 內容中有一個 id 數據,訪問的 URL 是:

  530.   /book?_method=read&id=123&title=xx
  531. 這兩個機制也能夠聯合使用:

  532.   var Book = $resource('/book/:id', {id: '@id'}, actions);  var book = Book.read({title: 'xx'}, {id: '123'}, function(response){
        console.log(response);
      });
  533. 結果就是出一個 POST 請求, postData 內容中有一個 id 數據,訪問的 URL 是:

  534.   /book/123?_method=read&title=xx
  535. 15.5. 實例

  536. ngResource 要舉一個實例是比較麻煩的事。由於它必需要一個後端來支持,這裏若是我用 Python 寫一個簡單的後端,估計要讓這個後端跑起來對不少人來講都是問題。因此,我在幾套公共服務的 API 中糾結考察了一番,最後使用 www.rememberthemilk.com 的 API 來作了一個簡單的,可用的例子。

  537. 例子見: http://zouyesheng.com/demo/ng-resource-demo.html (能夠直接下載看源碼)

  538. 先說一下 API 的狀況。這裏的請求調用全是跨域的,因此交互上所有是使用了 JSONP 的形式。 API 的使用有使用簽名認證機制,嗯, js 中直接算 md5 是可行的,我用了一個現成的庫(可是好像不能處理中文吧)。

  539. 這個例子中的 LoginCtrl 你們就不用太關心了,參見官方的文檔,走完流程拿到 token 完事。與ngResource 相關的是 MainCtrl 中的東西。

  540. 其實從這個例子中就能夠看出,目前 ngResource 的機制對於服務端返回的數據的格式是嚴重依賴的,同時也能夠反映出 $http 對一些場景根本沒法應對的侷限。因此,我如今的想法是理解ngResource 的思想,真正須要的人本身使用 jQuery 從新實現一遍也許更好。這應該也花不了多少時間, ngResource 的代碼原本很少。

  541. 我爲何說 $http 在一些場景中有侷限呢。在這個例子當中,全部的請求都須要帶一個簽名,簽名值是由請求中帶的參數根據規則使用 md5 方法計算出的值。我找不到一個 hook 可讓我在請求出去以前修改這個請求(添加上簽名)。因此在這個例子當中,個人作法是根據 ngResource 的請求最後會使用 $httpBackend 這個底層服務,在 module 定義時我本身複製官方的相關代碼,從新定義 $httpBackend 服務,在須要的地方作我本身的修改:

  542.   script.src = sign_url(url);
  543. 不錯,我就改了這一句,但我不得不復制了 50 行官方源碼到個人例子中。

  544. 另一個須要說的是對返回數據的處理。由於 ngResource 會使用返回的數據直接填充實例,因此這個數據格式就很重要。

  545. 首先,咱們可使用 $http.defaults.transformResponse 來統一處理一下返回的數據,可是這並不能解決全部問題,可目前 ngResource 並不提供對每個 action 的單獨的後處理回調函數項。除非你的服務端是通過專門的適應性設計的,不然你用 ngResource 不可能爽。例子中,我爲了獲取當前列表的結果,我不得不本身去封裝結果:

  546.   var list_list = List.getList(function(){    var res = list_list[1];    while(list_list.length > 0){list_list.pop()};
        angular.forEach(res.list, function(v){
          list_list.push(new List({list: v}));
        });
        $scope.list_list = list_list;
        $scope.show_add = true;    return;
      });
  547. 16. AngularJS與其它框架的混用(jQuery, Dojo)

  548. 這個問題彷佛不少人都關心,可是事實是,若是瞭解了 ng 的工做方式,這原本就不是一個問題了。

  549. 在我本身使用 ng 的過程中,一直是混用 jQuery 的,之前還要加上一個 Dojo 。只要瞭解每種框架的工做方式,在具體的代碼中每一個框架都作了什麼事,那麼總體上控制起來就不會有問題。

  550. 回到 ng 上來看,首先對於 jQuery 來講,最開始說提到過,在 DOM 操做部分, ng 與 jQuery 是兼容的,若是沒有 jQuery , ng 本身也實現了兼容的部分 API 。

  551. 同時,最開始也提到過, ng 的使用最忌諱的一點就是修改 DOM 結構——你應該使用 ng 的模板機制進行數據綁定,以此來控制 DOM 結構,而不是直接操做。換句話來講,在不動 DOM 結構的這個前提之下,你的數據隨便怎麼改,隨便使用哪一個框架來控制都是沒問題的,到時若有必要使用$scope.$digest() 來通知 ng 一下便可。

  552. 下面這個例子,咱們使用了 jQuery 中的 Deferred ( $.ajax 就是返回一個 Deferred ),還使用了 ng 的 $timeout ,固然是在 ng 的結構之下:

  553.  1   <!DOCTYPE html> 2   <html ng-app="Demo"> 3   <head> 4   <meta charset="utf-8" /> 5   <title>AngularJS</title> 6   </head> 7   <body> 8    9   <div ng-controller="TestCtrl">10     <span ng-click="go()">{{ a }}</span>11   </div>12   13   <script type="text/javascript"14     src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">15   </script>16   <script type="text/javascript"17     src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js">18   </script>19   20   <script type="text/javascript">21   var app = angular.module('Demo', [], angular.noop);22   app.controller('TestCtrl', function($scope, $timeout){23     $scope.a = '點擊我開始';24   25     var defer = $.Deferred();26     var f = function(){27       if($scope.a == ''){$scope.a = '已中止'; return}28       defer.done(function(){29         $scope.a.length < 10 ? $scope.a += '>' : $scope.a = '>';30         $timeout(f, 100);31       });32     }33     defer.done(function(){$scope.a = '>'; f()});34   35     $scope.go = function(){36       defer.resolve();37       $timeout(function(){$scope.a = ''}, 5000);38     }39   });40   </script>41   </body>42   </html>
  554. 再把 Dojo 加進來看與 DOM 結構相關的例子。以前說過,使用 ng 就最好不要手動修改 DOM 結構,但這裏說兩點:

  555. 對於整個頁面,你能夠只在局部使用 ng ,不使用 ng 的地方你能夠隨意控制 DOM 。

  556. 若是 DOM 結構有變更,你能夠在 DOM 結構定下來以後再初始化 ng 。

  557. 下面這個例子使用了 AngularJS , jQuery , Dojo :

  558.  1   <!DOCTYPE html> 2   <html> 3   <head> 4   <meta charset="utf-8" /> 5   <title>AngularJS</title> 6   <link rel="stylesheet" 7     href="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dijit/themes/claro/claro.css" media="screen" /> 8   </head> 9   <body class="claro">10   11   <div ng-controller="TestCtrl" id="test_ctrl">12   13     <p ng-show="!btn_disable">14       <button ng-click="change()">調用dojo修改按鈕</button>15     </p>16   17     <p id="btn_wrapper">18       <button data-dojo-type="dijit/form/Button" type="button">{{ a }}</button>19     </p>20   21     <p>22       <input ng-model="dialog_text" ng-init="dialog_text='對話框內容'" />23       <button ng-click="dialog(dialog_text)">顯示對話框</button>24     </p>25   26     <p ng-show="show_edit_text" style="display: none;">27       <span>須要編輯的內容:</span>28       <input ng-model="text" />29     </p>30   31     <div id="editor_wrapper">32       <div data-dojo-type="dijit/Editor" id="editor"></div>33     </div>34   35   </div>36   37   38   <script type="text/javascript"39     src="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js">40   </script>41   <script type="text/javascript"42     src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">43   </script>44   <script type="text/javascript"45     src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js">46   </script>47   48   <script type="text/javascript">49   50   require(['dojo/parser', 'dijit/Editor'], function(parser){51     parser.parse($('#editor_wrapper')[0]).then(function(){52       var app = angular.module('Demo', [], angular.noop);53   54       app.controller('TestCtrl', function($scope, $timeout){55         $scope.a = '我是ng, 也是dojo';56         $scope.show_edit_text = true;57   58         $scope.change = function(){59           $scope.a = 'DOM結構已經改變(不建議這樣作)';60           require(['dojo/parser', 'dijit/form/Button', 'dojo/domReady!'],61             function(parser){62               parser.parse($('#btn_wrapper')[0]);63               $scope.btn_disable = true;64             }65           );66         }67   68         $scope.dialog = function(text){69           require(["dijit/Dialog", "dojo/domReady!"], function(Dialog){70             var dialog = new Dialog({71                 title: "對話框哦",72                 content: text,73                 style: "width: 300px"74             });75             dialog.show();76           });77         }78   79         require(['dijit/registry'], function(registry){80           var editor = registry.byId('editor');81           $scope.$watch('text', function(new_v){82             editor.setValue(new_v);83           });84         });85   86       });87   88       angular.bootstrap(document, ['Demo']);89     });90   91   });92   93   </script>94   </body>95   </html>
  559. 17. 自定義過濾器

  560. 先來回顧一下 ng 中的一些概念:

  561. module ,代碼的組織單元,其它東西都是在定義在具體的模塊中的。

  562. app ,業務概念,可能會用到多個模塊。

  563. service ,僅在數據層面實現特定業務功能的代碼封裝。

  564. controller ,與 DOM 結構相關聯的東西,便是一種業務封裝概念,又體現了項目組織的層級結構。

  565. filter ,改變輸入數據的一種機制。

  566. directive ,與 DOM 結構相關聯的,特定功能的封裝形式。

  567. 上面的這幾個概念基本上就是 ng 的所有。每一部分均可以自由定義,使用時經過各要素的相互配合來實現咱們的業務需求。

  568. 咱們從最開始一致打交道的東西基本上都是 controller 層面的東西。在前面,也介紹了 module 和service 的自定義。剩下的會介紹 filter 和 directive 的定義。基本上這幾部分的定義形式都是同樣的,原理上是經過 provider 來作注入形式的聲明,在實際操做過程當中,又有不少 shortcut 式的聲明方式。

  569. 過濾器的自定義是最簡單的,就是一個函數,接受輸入,而後返回結果。在考慮過濾器時,我以爲很重要的一點: 無狀態 。

  570. 具體來講,過濾器就是一個函數,函數的本質含義就是肯定的輸入必定獲得肯定的輸出。雖然 filter是定義在 module 當中的,並且 filter 又是在 controller 的 DOM 範圍內使用的,可是,它和具體的module , controller , scope 這些概念都沒有關係(雖然在這裏你可使用 js 的閉包機制玩些花樣),它僅僅是一個函數,而已。換句話說,它沒有任何上下文關聯的能力。

  571. 過濾器基本的定義方式:

  572.   var app = angular.module('Demo', [], angular.noop);
      app.filter('map', function(){    var filter = function(input){      return input + '...';
        };    return filter;
      });
  573. 上面的代碼定義了一個叫作 map 的過濾器。使用時:

  574.   <p>示例數據: {{ a|map }}</p>
  575. 過濾器也能夠帶參數,多個參數之間使用 : 分割,看一個完整的例子:

  576.  1   <div ng-controller="TestCtrl"> 2   <p>示例數據: {{ a|map:map_value:'>>':'(no)' }}</p> 3   <p>示例數據: {{ b|map:map_value:'>>':'(no)' }}</p> 4   </div> 5    6    7   <script type="text/javascript"> 8    9   var app = angular.module('Demo', [], angular.noop);10   app.controller('TestCtrl', function($scope){11     $scope.map_value = {12       a: '一',13       b: '二',14       c: '三'15     }16     $scope.a = 'a';17   });18   19   app.filter('map', function(){20     var filter = function(input, map_value, append, default_value){21       var r = map_value[input];22       if(r === undefined){ return default_value + append }23       else { return r + append }24     };25     return filter;26   });27   28   angular.bootstrap(document, ['Demo']);29   </script>
  577. 18. 自定義指令directive

  578. 這是 ng 最強大的一部分,也是最複雜最讓人頭疼的部分。

  579. 目前咱們看到的所謂「模板」系統,只不過是官方實現的幾個指令而已。這意味着,經過自定義各類指令,咱們不但能夠徹底定義一套「模板」系統,更能夠把 HTML 頁面直接打形成爲一種 DSL (領域特定語言)。

  580. 18.1. 指令的使用

  581. 使用指令時,它的名字能夠有多種形式,把指令放在什麼地方也有多種選擇。

  582. 一般,指令的定義名是形如 ngBind 這樣的 「camel cased」 形式。在使用時,它的引用名能夠是:

  583. ng:bind

  584. ng_bind

  585. ng-bind

  586. x-ng-bind

  587. data-ng-bind

  588. 你能夠根據你本身是否有 「HTML validator」 潔癖來選擇。

  589. 指令能夠放在多個地方,它們的做用相同:

  590. <span my-dir="exp"></span> 做爲標籤的屬性

  591. <span class="my-dir: exp;"></span> 做爲標籤類屬性的值

  592. <my-dir></my-dir> 做爲標籤

  593. <!-- directive: my-dir exp --> 做爲註釋

  594. 這些方式可使用指令定義中的 restrict 屬性來控制。

  595. 能夠看出,指令便可以做爲標籤使用,也能夠做爲屬性使用。仔細考慮一下,這在類 XML 的結構當中真算得上是一種神奇的機制。

  596. 18.2. 指令的執行過程

  597. ng 中對指令的解析與執行過程是這樣的:

  598. 瀏覽器獲得 HTML 字符串內容,解析獲得 DOM 結構。

  599. ng 引入,把 DOM 結構扔給 $compile 函數處理:

    1. 找出 DOM 結構中有變量佔位符

    2. 匹配找出 DOM 中包含的全部指令引用

    3. 把指令關聯到 DOM

    4. 關聯到 DOM 的多個指令按權重排列

    5. 執行指令中的 compile 函數(改變 DOM 結構,返回 link 函數)

    6. 獲得的全部 link 函數組成一個列表做爲 $compile 函數的返回

  600. 執行 link 函數(鏈接模板的 scope)。

  601. 18.3. 基本的自定義方法

  602. 自定義一個指令能夠很是很是的複雜,可是其基本的調用形式,同自定義服務大概是相同的:

  603.   <p show style="font-size: 12px;"></p>  
      <script type="text/javascript">
      
      var app = angular.module('Demo', [], angular.noop);
      
      app.directive('show', function(){    var func = function($scope, $element, $attrs){
          console.log($scope);
          console.log($element);
          console.log($attrs);
        }    
        return func;    //return {compile: function(){return func}}
      });
      
      angular.bootstrap(document, ['Demo']);  </script>
  604. 若是在 directive 中直接返回一個函數,則這個函數會做爲 compile 的返回值,也便是做爲link 函數使用。這裏說的 compile 和 link 都是一個指令的組成部分,一個完整的定義應該返回一個對象,這個對象包括了多個屬性:

  605. name

  606. priority

  607. terminal

  608. scope

  609. controller

  610. require

  611. restrict

  612. template

  613. templateUrl

  614. replace

  615. transclude

  616. compile

  617. link

  618. 上面的每個屬性,均可以單獨探討的。

  619. 下面是一個完整的基本的指令定義例子:

  620.   <code lines>
      //失去焦點使用 jQuery 的擴展支持冒泡
      app.directive('ngBlur', function($parse){
        return function($scope, $element, $attr){
          var fn = $parse($attr['ngBlur']);
          $element.on('focusout', function(event){
            fn($scope, {$event: event});
          });
        }
      });
      </code>
      
      <div code lines>
      //失去焦點使用 jQuery 的擴展支持冒泡
      app.directive('ngBlur', function($parse){
        return function($scope, $element, $attr){
          var fn = $parse($attr['ngBlur']);
          $element.on('focusout', function(event){
            fn($scope, {$event: event});
          });
        }
      });
      </div>
  621.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('code', function(){ 4     var func = function($scope, $element, $attrs){ 5    6       var html = $element.text(); 7       var lines = html.split('\n'); 8    9       //處理首尾空白10       if(lines[0] == ''){lines = lines.slice(1, lines.length - 1)}11       if(lines[lines.length-1] == ''){lines = lines.slice(0, lines.length - 1)}12   13       $element.empty();14   15       //處理外框16       (function(){17         $element.css('clear', 'both');18         $element.css('display', 'block');19         $element.css('line-height', '20px');20         $element.css('height', '200px');21       })();22   23       //是否顯示行號的選項24       if('lines' in $attrs){25         //處理行號26         (function(){27           var div = $('<div style="width: %spx; background-color: gray; float: left; text-align: right; padding-right: 5px; margin-right: 10px;"></div>'28                       .replace('%s', String(lines.length).length * 10));29           var s = '';30           angular.forEach(lines, function(_, i){31             s += '<pre style="margin: 0;">%s</pre>\n'.replace('%s', i + 1);32           });33           div.html(s);34           $element.append(div);35         })();36       }37   38       //處理內容39       (function(){40         var div = $('<div style="float: left;"></div>');41         var s = '';42         angular.forEach(lines, function(l){43           s += '<span style="margin: 0;">%s</span><br />\n'.replace('%s', l.replace(/\s/g, '<span>&nbsp;</span>'));44         });45         div.html(s);46         $element.append(div);47       })();48     }49   50     return {link: func,51             restrict: 'AE'}; //以元素或屬性的形式使用命令52   });53   54   angular.bootstrap(document, ['Demo']);
  622. 上面這個自定義的指令,作的事情就是解析節點中的文本內容,而後修改它,再把生成的新內容填充到節點當中去。其間還涉及了節點屬性值 lines 的處理。這算是指令中最簡單的一種形式。由於它是「一次性使用」,中間沒有變量的處理。好比若是節點原來的文本內容是一個變量引用,相似於{{ code }} ,那上面的代碼就不行了。這種狀況麻煩得多。後面會討論。

  623. 18.4. 屬性值類型的自定義

  624. 官方代碼中的 ng-show 等算是我說的這種類型。使用時主要是在節點加添加一個屬性值以附加額外的功能。看一個簡單的例子:

  625.   <p color="red">有顏色的文本</p>
      <color color="red">有顏色的文本</color>
      
      <script type="text/javascript">  
      var app = angular.module('Demo', [], angular.noop);
      
      app.directive('color', function(){    var link = function($scope, $element, $attrs){
          $element.css('color', $attrs.color);
        }    return {link: link,
                restrict: 'AE'};
      });
      
      angular.bootstrap(document, ['Demo']);
      </script>
  626. 咱們定義了一個叫 color 的指令,能夠指定節點文本的顏色。可是這個例子還沒法像 ng-show 那樣工做的,這個例子只能渲染一次,而後就沒法根據變量來從新改變顯示了。要響應變化,咱們須要手工使用 scope 的 $watch 來處理:

  627.  1    2   <div ng-controller="TestCtrl"> 3     <p color="color">有顏色的文本</p> 4     <p color="'blue'">有顏色的文本</p> 5   </div> 6    7   <script type="text/javascript"> 8    9   var app = angular.module('Demo', [], angular.noop);10   11   app.directive('color', function(){12     var link = function($scope, $element, $attrs){13       $scope.$watch($attrs.color, function(new_v){14         $element.css('color', new_v);15       });16     }17     return link;18   });19   20   app.controller('TestCtrl', function($scope){21     $scope.color = 'red';22   });23   24   angular.bootstrap(document, ['Demo']);25   </script>
  628. 18.5. Compile的細節

  629. 指令的處理過程,是 ng 的 Compile 過程的一部分,它們也是緊密聯繫的。繼續深刻指令的定義方法,首先就要對 Compile 的過程作更細緻的瞭解。

  630. 前面說過, ng 對頁面的處理過程:

  631. 瀏覽器把 HTML 字符串解析成 DOM 結構。

  632. ng 把 DOM 結構給 $compile ,返回一個 link 函數。

  633. 傳入具體的 scope 調用這個 link 函數。

  634. 獲得處理後的 DOM ,這個 DOM 處理了指令,鏈接了數據。

  635. $compile 最基本的使用方式:

  636.   var link = $compile('<p>{{ text }}</p>');  var node = link($scope);
      console.log(node);
  637. 上面的 $compile 和 link 調用時都有額外參數來實現其它功能。先看 link 函數,它形如:

  638.   function(scope[, cloneAttachFn]
  639. 第二個參數 cloneAttachFn 的做用是,代表是否複製原始節點,及對複製節點須要作的處理,下面這個例子說明了它的做用:

  640.   <div ng-controller="TestCtrl"></div>
      <div id="a">A {{ text }}</div>
      <div id="b">B </div>
  641.   app.controller('TestCtrl', function($scope, $compile){    var link = $compile($('#a'));  
        //true參數表示新建一個徹底隔離的scope,而不是繼承的child scope
        var scope = $scope.$new(true);
        scope.text = '12345';  
        //var node = link(scope, function(){});
        var node = link(scope);
      
        $('#b').append(node);
      });
  642. cloneAttachFn 對節點的處理是有限制的,你能夠添加 class ,可是不能作與數據綁定有關的其它修改(修改了也無效):

  643.   app.controller('TestCtrl', function($scope, $compile){    var link = $compile($('#a'));    var scope = $scope.$new(true);
        scope.text = '12345';  
        var node = link(scope, function(clone_element, scope){
          clone_element.text(clone_element.text() + ' ...'); //無效
          clone_element.text('{{ text2 }}'); //無效
          clone_element.addClass('new_class');
        });
      
        $('#b').append(node);
      });
  644. 修改無效的緣由是,像 {{ text }} 這種所謂的 Interpolate 在 $compile 中已經被處理過了,生成了相關函數(這裏起做用的是 directive 中的一個 postLink 函數),後面執行 link 就是執行了 $compile 生成的這些函數。固然,若是你的文本沒有數據變量的引用,那修改是會有效果的。

  645. 前面在說自定義指令時說過, link 函數是由 compile 函數返回的,也就像前面說的,應該把改變 DOM 結構的邏輯放在 compile 函數中作。

  646. $compile 還有兩個額外的參數:

  647.   $compile(element, transclude, maxPriority);
  648. maxPriority 是指令的權重限制,這個容易理解,後面再說。

  649. transclude 是一個函數,這個函數會傳遞給 compile 期間找到的 directive 的 compile 函數(編譯節點的過程當中找到了指令,指令的 compile 函數會接受編譯時傳遞的 transclude 函數做爲其參數)。

  650. 可是在實際使用中,除咱們手工在調用 $compile 以外,初始化時的根節點 compile 是不會傳遞這個參數的。

  651. 在咱們定義指令時,它的 compile 函數是這個樣子的:

  652.   function compile(tElement, tAttrs, transclude) { ... }
  653. 事實上, transclude 的值,就是 directive 所在的 原始 節點,把原始節點從新作了編譯以後獲得的 link 函數(須要 directive 定義時使用 transclude 選項),後面會專門演示這個過程。因此,官方文檔上也把 transclude 函數描述成 link 函數的樣子(若是自定義的指令只用在本身手動 $compile 的環境中,那這個函數的形式是能夠隨意的):

  654.   {function(angular.Scope[, cloneAttachFn]}
  655. 因此記住,定義指令時, compile 函數的第三個參數 transclude ,就是一個 link ,裝入scope 執行它你就獲得了一個節點。

  656. 18.6. transclude的細節

  657. transclude 有兩方面的東西,一個是使用 $compile 時傳入的函數,另外一個是定義指令的compile 函數時接受的一個參數。雖然這裏的一出一進原本是相互對應的,可是實際使用中,由於大部分時候不會手動調用 $compile ,因此,在「默認」狀況下,指令接受的 transclude 又會是一個比較特殊的函數。

  658. 看一個基本的例子:

  659.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('more', function(){    var func = function(element, attrs, transclude){      var sum = transclude(1, 2);
          console.log(sum);
          console.log(element);  
        }  
        return {compile: func,
                restrict: 'E'};
      });
      
      app.controller('TestCtrl', function($scope, $compile, $element){    var s = '<more>123</more>';    var link = $compile(s, function(a, b){return a + b});    var node = link($scope);
        $element.append(node);
      });
      
      angular.bootstrap(document, ['Demo']);
  660. 咱們定義了一個 more 指令,它的 compile 函數的第三個參數,就是咱們手工 $compile 時傳入的。

  661. 若是不是手工 $compile ,而是 ng 初始化時找出的指令,則 transclude 是一個 link 函數(指令定義須要設置 transclude 選項):

  662.   <div more>123</div>
  663.   app.directive('more', function($rootScope, $document){    var func = function(element, attrs, link){      var node = link($rootScope);
          node.removeAttr('more'); //不去掉就變死循環了
          $('body', $document).append(node);
        }  
        return {compile: func,
                transclude: 'element', // element是節點沒,其它值是節點的內容沒
                restrict: 'A'};
      });
  664. 18.7. 把節點內容做爲變量處理的類型

  665. 回顧最開始的那個代碼顯示的例子,那個例子只能處理一次節點內容。若是節點的內容是一個變量的話,須要用另外的思路來考慮。這裏咱們假設的例子是,定義一個指令 showLenght ,它的做用是在一段文本的開頭顯示出這段節點文本的長度,節點文本是一個變量。指令使用的形式是:

  666.   <div ng-controller="TestCtrl">
        <div show-length>{{ text }}</div>
        <button ng-click="text='xx'">改變</button>
      </div>
  667. 從上面的 HTML 代碼中,大概清楚 ng 解析它的過程(只看 show-length 那一行):

  668. 解析 div 時發現了一個 show-length 的指令。

  669. 若是 show-length 指令設置了 transclude 屬性,則 div 的節點內容被從新編譯,獲得的 link 函數做爲指令 compile 函數的參數傳入。

  670. 若是 show-length 指令沒有設置 transclude 屬性,則繼續處理它的子節點(TextNode )。

  671. 無論是上面的哪一種狀況,都會繼續處理到 {{ text }} 這段文本。

  672. 發現 {{ text }} 是一個 Interpolate ,因而自動在此節點中添加了一個指令,這個指令的 link 函數就是爲 scope 添加了一個 $watch ,實現的功能是是當 scope 做$digest 的時候,就更新節點文本。

  673. 與處理 {{ text }} 時添加的指令相同,咱們實現 showLength 的思路,也就是:

  674. 修改原來的 DOM 結構

  675. 爲 scope 添加 $watch ,當 $digest 時修改指定節點的文本,其值爲指定節點文本的長度。

  676. 代碼以下:

  677.   app.directive('showLength', function($rootScope, $document){    var func = function(element, attrs, link){  
          return function(scope, ielement, iattrs, controller){        var node = link(scope);
            ielement.append(node);        var lnode = $('<span></span>');
            ielement.prepend(lnode);
      
            scope.$watch(function(scope){
              lnode.text(node.text().length);
            });
          };
        }  
        return {compile: func,
                transclude: true, // element是節點沒,其它值是節點的內容沒
                restrict: 'A'};
      });
  678. 上面代碼中,由於設置了 transclude 屬性,咱們在 showLength 的 link 函數(就是 return的那個函數)中,使用 func 的第三個函數來重塑了原來的文本節點,並放在咱們須要的位置上。而後,咱們添加本身的節點來顯示長度值。最後給當前的 scope 添加 $watch ,以更新這個長度值。

  679. 18.8. 指令定義時的參數

  680. 指令定義時的參數以下:

  681. name

  682. priority

  683. terminal

  684. scope

  685. controller

  686. require

  687. restrict

  688. template

  689. templateUrl

  690. replace

  691. transclude

  692. compile

  693. link

  694. 如今咱們開始一個一個地吃掉它們……,可是並非按順序講的。

  695. priority

  696. 這個值設置指令的權重,默認是 0 。當一個節點中有多個指令存在時,就按着權限從大到小的順序依次執行它們的 compile 函數。相同權重順序不定。

  697. terminal

  698. 是否以當前指令的權重爲結束界限。若是這值設置爲 true ,則節點中權重小於當前指令的其它指令不會被執行。相同權重的會執行。

  699. restrict

  700. 指令能夠以哪些方式被使用,能夠同時定義多種方式。

    1. E 元素方式 <my-directive></my-directive>

    2. A 屬性方式 <div my-directive="exp"> </div>

    3. C 類方式 <div class="my-directive: exp;"></div>

    4. M 註釋方式 <!-- directive: my-directive exp -->

  701. transclude

  702. 前面已經講過基本的用法了。能夠是 'element' 或 true 兩種值。

  703. compile

  704. 基本的定義函數。 function compile(tElement, tAttrs, transclude) { ... }

  705. link

  706. 前面介紹過了。大多數時候咱們不須要單獨定義它。只有 compile 未定義時 link 纔會被嘗試。function link(scope, iElement, iAttrs, controller) { ... }

  707. scope

  708. scope 的形式。 false 節點的 scope , true 繼承建立一個新的 scope , {} 不繼承建立一個新的隔離 scope 。 {@attr: '引用節點屬性', =attr: '把節點屬性值引用成scope屬性值', &attr: '把節點屬性值包裝成函數'}

  709. controller

  710. 爲指令定義一個 controller , function controller($scope, $element, $attrs, $transclude) { ... }

  711. name

  712. 指令的 controller 的名字,方便其它指令引用。

  713. require

  714. 要引用的其它指令 conroller 的名字, ?name 忽略不存在的錯誤, ^name 在父級查找。

  715. template

  716. 模板內容。

  717. templateUrl

  718. 從指定地址獲取模板內容。

  719. replace

  720. 是否使用模板內容替換掉整個節點, true 替換整個節點, false 替換節點內容。

  721.   <a b></a>
  722.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('a', function(){    var func = function(element, attrs, link){
          console.log('a');
        }  
        return {compile: func,
                priority: 1,
                restrict: 'EA'};
      });
      
      app.directive('b', function(){    var func = function(element, attrs, link){
          console.log('b');
        }  
        return {compile: func,
                priority: 2,            //terminal: true,
                restrict: 'A'};
      });
  723. 上面幾個參數值都是比較簡單且容易理想的。

  724. 再看 scope 這個參數:

  725.   <div ng-controller="TestCtrl">
        <div a b></div>
      </div>
  726.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(element, attrs, link){ 5       return function(scope){ 6         console.log(scope); 7       } 8     } 9   10     return {compile: func,11             scope: true,12             restrict: 'A'};13   });14   15   app.directive('b', function(){16     var func = function(element, attrs, link){17       return function(scope){18         console.log(scope);19       }20     }21   22     return {compile: func,23             restrict: 'A'};24   });25   26   app.controller('TestCtrl', function($scope){27     $scope.a = '123';28     console.log($scope);29   });
  727. 對於 scope :

  728. 默認爲 false , link 函數接受的 scope 爲節點所在的 scope 。

  729. 爲 true 時,則 link 函數中第一個參數(還有 controller 參數中的 $scope ),scope 是節點所在的 scope 的 child scope ,而且若是節點中有多個指令,則只要其中一個指令是 true 的設置,其它全部指令都會受影響。

  730. 這個參數還有其它取值。當其爲 {} 時,則 link 接受一個徹底隔離(isolate)的 scope ,於true 的區別就是不會繼承其它 scope 的屬性。可是這時,這個 scope 的屬性卻能夠有很靈活的定義方式:

  731. @attr 引用節點的屬性。

  732.   <div ng-controller="TestCtrl">
        <div a abc="here" xx="{{ a }}" c="ccc"></div>
      </div>
  733.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('a', function(){    var func = function(element, attrs, link){      return function(scope){
            console.log(scope);
          }
        }  
        return {compile: func,
                scope: {a: '@abc', b: '@xx', c: '@'},
                restrict: 'A'};
      });
      
      app.controller('TestCtrl', function($scope){
        $scope.a = '123';
      });
  734. @abc  引用 div 節點的 abc 屬性。

  735. @xx  引用 div 節點的 xx 屬性,而 xx 屬性又是一個變量綁定,因而 scope 中 b 屬性值就和 TestCtrl 的 a 變量綁定在一塊兒了。

  736. @ 沒有寫 attr name ,則默認取本身的值,這裏是取 div 的 c 屬性。

  737. =attr 類似,只是它把節點的屬性值當成節點 scope 的屬性名來使用,做用至關於上面例子中的@xx :

  738.   <div ng-controller="TestCtrl">
        <div a abc="here"></div>
      </div>
  739.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('a', function(){    var func = function(element, attrs, link){      return function(scope){
            console.log(scope);
          }
        }  
        return {compile: func,
                scope: {a: '=abc'},
                restrict: 'A'};
      });
      
      app.controller('TestCtrl', function($scope){
        $scope.here = '123';
      });
  740. &attr 是包裝一個函數出來,這個函數以節點所在的 scope 爲上下文。來看一個很爽的例子:

  741.   <div ng-controller="TestCtrl">
        <div a abc="here = here + 1" ng-click="show(here)">這裏</div>
        <div>{{ here }}</div>
      </div>
  742.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(element, attrs, link){ 5       return function llink(scope){ 6         console.log(scope); 7         scope.a(); 8         scope.b(); 9   10         scope.show = function(here){11           console.log('Inner, ' + here);12           scope.a({here: 5});13         }14       }15     }16   17     return {compile: func,18             scope: {a: '&abc', b: '&ngClick'},19             restrict: 'A'};20   });21   22   app.controller('TestCtrl', function($scope){23     $scope.here = 123;24     console.log($scope);25   26     $scope.show = function(here){27       console.log(here);28     }29   });
  743. scope.a 是 &abc ,即:

  744.   scope.a = function(){here = here + 1}
  745. 只是其中的 here 是 TestCtrl 的。

  746. scope.b 是 &ngClick ,即:

  747.   scope.b = function(){show(here)}
  748. 這裏的 show() 和 here 都是 TestCtrl 的,因而上面的代碼最開始會在終端輸出一個 124 。

  749. 當點擊「這裏」時,這時執行的 show(here) 就是 llink 中定義的那個函數了,與 TestCtrl 無關。可是,其間的 scope.a({here:5}) ,由於 a 執行時是 TestCtrl 的上下文,因而向 a 傳遞的一個對象,裏面的全部屬性 TestCtrl 就全收下了,接着執行 here=here+1 ,因而咱們會在屏幕上看到 6 。

  750. 這裏是一個上下文交錯的環境,經過 & 這種機制,讓指令的 scope 與節點的 scope 發生了互動。真是鬼斧神工的設計。而實現它,只用了幾行代碼:

  751.   case '&': {
        parentGet = $parse(attrs[attrName]);
        scope[scopeName] = function(locals) {      return parentGet(parentScope, locals);
        }    break;
      }
  752. 再看 controller 這個參數。這個參數的做用是提供一個 controller 的構造函數,它會在 compile 函數以後, link 函數以前被執行。

  753.   <a>haha</a>
  754.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(){ 5       console.log('compile'); 6       return function(){ 7         console.log('link'); 8       } 9     }10   11     var controller = function($scope, $element, $attrs, $transclude){12       console.log('controller');13       console.log($scope);14   15       var node = $transclude(function(clone_element, scope){16         console.log(clone_element);17         console.log('--');18         console.log(scope);19       });20       console.log(node);21     }22   23     return {compile: func,24             controller: controller,25             transclude: true,26             restrict: 'E'}27   });
  755. controller 的最後一個參數, $transclude ,是一個只接受 cloneAttachFn 做爲參數的一個函數。

  756. 按官方的說法,這個機制的設計目的是爲了讓各個指令之間能夠互相通訊。參考普通節點的處理方式,這裏也是處理指令 scope 的合適位置。

  757.   <a b>kk</a>
  758.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(){ 5     } 6    7     var controller = function($scope, $element, $attrs, $transclude){ 8       console.log('a'); 9       this.a = 'xx';10     }11   12     return {compile: func,13             name: 'not_a',14             controller: controller,15             restrict: 'E'}16   });17   18   app.directive('b', function(){19     var func = function(){20       return function($scope, $element, $attrs, $controller){21         console.log($controller);22       }23     }24   25     var controller = function($scope, $element, $attrs, $transclude){26       console.log('b');27     }28   29     return {compile: func,30             controller: controller,31             require: 'not_a',32             restrict: 'EA'}33   });
  759. name 參數在這裏能夠用覺得 controller 重起一個名字,以方便在 require 參數中引用。

  760. require 參數能夠帶兩種前綴(能夠同時使用):

  761. ? ,若是指定的 controller 不存在,則忽略錯誤。即:

      require: '?not_b'

    若是名爲 not_b 的 controller 不存在時,不會直接拋出錯誤, link 函數中對應的$controller 爲 undefined 。

  762. ^ ,同時在父級節點中尋找指定的 controller ,把上面的例子小改一下:

      <a><b>kk</b></a>

    把 a 的 require 改爲(不然就找不到 not_a 這個 controller ):

      require: '?^not_a'
  763. 還剩下幾個模板參數:

  764. template 模板內容,這個內容會根據 replace 參數的設置替換節點或只替換節點內容。


  765. templateUrl 模板內容,獲取方式是異步請求。


  766. replace 設置如何處理模板內容。爲 true 時爲替換掉指令節點,不然只替換到節點內容。


  767.   <div ng-controller="TestCtrl">
        <h1 a>原始內容</h1>
      </div>
  768.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('a', function(){    var func = function(){
        }  
        return {compile: func,
                template: '<p>標題 {{ name }} <button ng-click="name=\'hahaha\'">修改</button></p>',            //replace: true,
                //controller: function($scope){$scope.name = 'xxx'},
                //scope: {},
                scope: true ,
                controller: function($scope){console.log($scope)},
                restrict: 'A'}
      });
      
      app.controller('TestCtrl', function($scope){
        $scope.name = '123';
        console.log($scope);
      });
  769. template 中能夠包括變量引用的表達式,其 scope 遵尋 scope 參數的做用(可能受繼承關係影響)。

  770. templateUrl 是異步請求模板內容,而且是獲取到內容以後纔開始執行指令的 compile 函數。

  771. 最後說一個 compile 這個參數。它除了能夠返回一個函數用爲 link 函數以外,還能夠返回一個對象,這個對象能包括兩個成員,一個 pre ,一個 post 。實際上, link 函數是由兩部分組成,所謂的 preLink 和 postLink 。區別在於執行順序,特別是在指令層級嵌套的結構之下, postLink 是在全部的子級指令 link 完成以後才最後執行的。 compile 若是隻返回一個函數,則這個函數被做爲 postLink 使用:

  772.   <a><b></b></a>
  773.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(){ 5       console.log('a compile'); 6       return { 7         pre: function(){console.log('a link pre')}, 8         post: function(){console.log('a link post')}, 9       }10     }11   12     return {compile: func,13             restrict: 'E'}14   });15   16   app.directive('b', function(){17     var func = function(){18       console.log('b compile');19       return {20         pre: function(){console.log('b link pre')},21         post: function(){console.log('b link post')},22       }23     }24   25     return {compile: func,26             restrict: 'E'}27   });
  774. 18.9. Attributes的細節

  775. 節點屬性被包裝以後會傳給 compile 和 link 函數。從這個操做中,咱們能夠獲得節點的引用,能夠操做節點屬性,也能夠爲節點屬性註冊偵聽事件。

  776.   <test a="1" b c="xxx"></test>
  777.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('test', function(){    var func = function($element, $attrs){
          console.log($attrs);
        }  
        return {compile: func,
                restrict: 'E'}
  778. 整個 Attributes 對象是比較簡單的,它的成員包括了:

  779. $$element 屬性所在的節點。


  780. $attr 全部的屬性值(類型是對象)。


  781. $normalize 一個名字標準化的工具函數,能夠把 ng-click 變成 ngClick 。


  782. $observe 爲屬性註冊偵聽器的函數。


  783. $set 設置對象屬性,及節點屬性的工具。


  784. 除了上面這些成員,對象的成員還包括全部屬性的名字。

  785. 先看 $observe 的使用,基本上至關於 $scope 中的 $watch :

  786.   <div ng-controller="TestCtrl">
        <test a="{{ a }}" b c="xxx"></test>
        <button ng-click="a=a+1">修改</button>
      </div>
  787.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('test', function(){    var func = function($element, $attrs){
          console.log($attrs);
      
          $attrs.$observe('a', function(new_v){
            console.log(new_v);
          });
        }  
        return {compile: func,
                restrict: 'E'}
      });
      
      app.controller('TestCtrl', function($scope){
        $scope.a = 123;
      });
  788. $set 方法的定義是: function(key, value, writeAttr, attrName) { ... } 。

  789. key 對象的成員名。

  790. value 須要設置的值。

  791. writeAttr 是否同時修改 DOM 節點的屬性(注意區別「節點」與「對象」),默認爲 true

  792. attrName 實際的屬性名,與「標準化」以後的屬性名有區別。

  793.   <div ng-controller="TestCtrl">
        <test a="1" ys-a="123" ng-click="show(1)">這裏</test>
      </div>
  794.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('test', function(){    var func = function($element, $attrs){
          $attrs.$set('b', 'ooo');
          $attrs.$set('a-b', '11');
          $attrs.$set('c-d', '11', true, 'c_d');
          console.log($attrs);
        }  
        return {compile: func,
                restrict: 'E'}
      });
      
      app.controller('TestCtrl', function($scope){
        $scope.show = function(v){console.log(v);}
      });
  795. 從例子中能夠看到,原始的節點屬性值對,放到對象中以後,名字必定是「標準化」以後的。可是手動$set 的新屬性,不會自動作標準化處理。

  796. 18.10. 預約義的 NgModelController

  797. 在前面講 conroller 參數的時候,提到過能夠爲指令定義一個 conroller 。官方的實現中,有不少已定義的指令,這些指令當中,有兩個已定義的 conroller ,它們是 NgModelController 和FormController ,對應 ng-model 和 form 這兩個指令(能夠參照前面的「表單控件」一章)。

  798. 在使用中,除了能夠經過 $scope 來取得它們的引用以外,也能夠在自定義指令中經過 require 參數直接引用,這樣就能夠在 link 函數中使用 controller 去實現一些功能。

  799. 先看 NgModelController 。這東西的做用有兩個,一是控制 ViewValue 與 ModelValue 之間的轉換關係(你能夠實現看到的是一個值,可是存到變量裏變成了另一個值),二是與FormController 配合作數據校驗的相關邏輯。

  800. 先看兩個應該是最有用的屬性:

  801. $formatters 是一個由函數組成的列表,串行執行,做用是把變量值變成顯示的值。


  802. $parsers 與上面的方向相反,把顯示的值變成變量值。


  803. 假設咱們在變量中要保存一個列表的類型,可是顯示的東西只能是字符串,因此這二者之間須要一個轉換:

  804.   <div ng-controller="TestCtrl">
        <input type="text" ng-model="a" test />
        <button ng-click="show(a)">查看</button>
      </div>
  805.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('test', function(){ 4     var link = function($scope, $element, $attrs, $ctrl){ 5    6       $ctrl.$formatters.push(function(value){ 7         return value.join(','); 8       }); 9   10       $ctrl.$parsers.push(function(value){11         return value.split(',');12       });13     }14   15     return {compile: function(){return link},16             require: 'ngModel',17             restrict: 'A'}18   });19   20   app.controller('TestCtrl', function($scope){21     $scope.a = [];22     //$scope.a = [1,2,3];23     $scope.show = function(v){24       console.log(v);25     }26   });
  806. 上面在定義 test 這個指令, require 參數指定了 ngModel 。同時由於 DOM 結構, ng-model是存在的。因而, link 函數中就能夠獲取到一個 NgModelController 的實例,即代碼中的 $ctrl

  807. 咱們添加了須要的過濾函數:

  808. 從變量( ModelValue )到顯示值( ViewValue )的過程, $formatters 屬性,把一個列表變成一個字符串。

  809. 從顯示值到變量的過程, $parsers 屬性,把一個字符串變成一個列表。

  810. 對於顯示值和變量,還有其它的 API ,這裏就不細說了。

  811. 另外一部分,是關於數據校驗的,放到下一章同 FormController 一塊兒討論。

  812. 18.11. 預約義的 FormController

  813. 前面的「表單控制」那章,實際上講的就是 FormController ,只是那裏是從 scope 中獲取到的引用。如今從指令定義的角度,來更清楚地瞭解 FormController 及 NgModelController 是如何配合工做的。

  814. 先說一下, form 和 ngForm 是官方定義的兩個指令,可是它們實際上是同一個東西。前者只容許以標籤形式使用,然後者容許 EAC 的形式。DOM 結構中, form 標籤不能嵌套,可是 ng 的指令沒有這個限制。無論是 form 仍是 ngForm ,它們的 controller 都被命名成了 form 。 因此require 這個參數不要寫錯了。

  815. FormController 的幾個成員是很好理解的:

  816. $pristine 表單是否被動過


  817. $dirty 表單是否沒被動過


  818. $valid 表單是否檢驗經過


  819. $invalid 表單是否檢驗未經過


  820. $error 表單中的錯誤


  821. $setDirty() 直接設置 $dirty 及 $pristine


  822.   <div ng-controller="TestCtrl">
        <div ng-form test>
          <input ng-model="a" type="email" />
          <button ng-click="do()">查看</button>
        </div>
      </div>
  823.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('test', function(){    var link = function($scope, $element, $attrs, $ctrl){
          $scope.do = function(){        //$ctrl.$setDirty();
            console.log($ctrl.$pristine); //form是否沒被動過
            console.log($ctrl.$dirty); //form是否被動過
            console.log($ctrl.$valid); //form是否被檢驗經過
            console.log($ctrl.$invalid); //form是否有錯誤
            console.log($ctrl.$error); //form中有錯誤的字段
          }
        }  
        return {compile: function(){return link},
                require: 'form',
                restrict: 'A'}
      });
      
      app.controller('TestCtrl', function($scope){
      });
  824. $error 這個屬性,是一個對象, key 是錯誤名, value 部分是一個列表,其成員是對應的NgModelController 的實例。

  825. FormController 能夠自由增減它包含的那些,相似於 NgModelController 的實例。在 DOM 結構上,有 ng-model 的 input 節點的 NgMoelController 會被自動添加。

  826. $addControl() 添加一個 conroller


  827. $removeControl() 刪除一個 controller


  828. 這兩個手動使用機會應該不會不少。被添加的實例也能夠手動實現全部的 NgModelController 的方法

  829.   <div ng-controller="TestCtrl">
        <bb />
        <div ng-form test>
          <input ng-model="a" type="email" />
          <button ng-click="add()">添加</button>
        </div>
      </div>
  830.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('test', function(){ 4     var link = function($scope, $element, $attrs, $ctrl){ 5       $scope.add = function(){ 6         $ctrl.$addControl($scope.bb); 7         console.log($ctrl); 8       } 9     }10   11     return {compile: function(){return link},12             require: 'form',13             restrict: 'A'}14   });15   16   app.directive('bb', function(){17     var controller = function($scope, $element, $attrs, $transclude){18       $scope.bb = this;19       this.$name = 'bb';20     }21   22     return {compile: angular.noop,23             restrict: 'E',24             controller: controller}25   });26   27   app.controller('TestCtrl', function($scope){28   });
  831. 整合 FormController 和 NgModelController 就很容易擴展各類類型的字段:

  832.   <div ng-controller="TestCtrl">
        <form name="f">
          <input type="my" ng-model="a" />
          <button ng-click="show()">查看</button>
        </form>
      </div>
  833.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('input', function(){ 4     var link = function($scope, $element, $attrs, $ctrl){ 5       console.log($attrs.type); 6       var validator = function(v){ 7         if(v == '123'){ 8           $ctrl.$setValidity('my', true); 9           return v;10         } else {11           $ctrl.$setValidity('my', false);12           return undefined;13         }14       }15   16       $ctrl.$formatters.push(validator);17       $ctrl.$parsers.push(validator);18     }19   20     return {compile: function(){return link},21             require: 'ngModel',22             restrict: 'E'}23   });24   25   app.controller('TestCtrl', function($scope){26       $scope.show = function(){27         console.log($scope.f);28       }29   });
  834. 雖然官方原來定義了幾種 type ,但這不妨礙咱們繼續擴展新的類型。若是新的 type 參數值不在官方的定義列表裏,那會按 text 類型先作處理,這其實什麼影響都沒有。剩下的,就是寫咱們本身的驗證邏輯就好了。

  835. 上面的代碼是參見官方的作法,使用格式化的過程,同時在裏面作有效性檢查。

  836. 18.12. 示例:文本框

  837. 這個例子與官網上的那個例子類似。最終是要顯示一個文本框,這個文本框由標題和內容兩部分組成。並且標題和內容則是引用 controller 中的變量值。

  838. HTML 部分的代碼:

  839.   <div ng-controller="TestCtrl">
        <ys-block title="title" text="text"></ys-block>
        <p>標題: <input ng-model="title" /></p>
        <p>內容: <input ng-model="text" /></p>
        <ys-block title="title" text="text"></ys-block>
      </div>
  840. 從這個指望實現效果的 HTML 代碼中,咱們能夠考慮設計指令的實現方式:

  841. 這個指令的使用方式是「標籤」, 即 restrict 這個參數應該設置爲 E 。

  842. 節點的屬性值是對 controller 變量的引用,那麼咱們應該在指令的 scope 中使用 = 的方式來指定成員值。

  843. 最終的效果顯示須要進行 DOM 結構的重構,那直接使用 template 就行了。

  844. 自定義的標籤在最終效果中是多餘的,全部 replace 應該設置爲 true 。

  845. JS 部分的代碼:

  846.   var app = angular.module('Demo', [], angular.noop);
      
      app.directive('ysBlock', function(){    return {compile: angular.noop,
                template: '<div style="width: 200px; border: 1px solid black;"><h1 style="background-color: gray; color: white; font-size: 22px;">{{ title }}</h1><div>{{ text }}</div></div>',
                replace: true,
                scope: {title: '=title', text: '=text'},
                restrict: 'E'};
      });
      
      app.controller('TestCtrl', function($scope){
        $scope.title = '標題在這裏';
        $scope.text = '內容在這裏';
      });
      
      angular.bootstrap(document, ['Demo']);
  847. 能夠看到,這種簡單的組件式指令,只須要做 DOM 結構的變換便可實現,連 compile 函數都不須要寫。

  848. 18.13. 示例:模板控制語句 for

  849. 這個示例嘗試實現一個重複語句,功能同官方的 ngRepeat ,可是使用方式相似於咱們一般編程語言中的 for 語句:

  850.   <div ng-controller="TestCtrl" ng-init="obj_list=[1,2,3,4]; name='name'">
        <ul>
          <for o in obj_list>
            <li>{{ o }}, {{ name }}</li>
          </for>
        </ul>
        <button ng-click="obj_list=[1,2]; name='o?'">修改</button>
      </div>
  851. 一樣,咱們從上面的使用方式去考慮這個指令的實現:

  852. 這是一個徹底的控制指令,因此單個節點應該只有它一個指令起做用就行了,因而權重要比較高,而且「到此爲止」—— priority 設置爲 1000 , terminal 設置爲 true 。

  853. 使用時的語法問題。事實上瀏覽器會把 for 節點補充成一個正確的 HTML 結構,即裏面的屬性都會變成相似 o="" 這樣。咱們經過節點的 outerHTML 屬性取到字符串並解析取得須要的信息。

  854. 咱們把 for 節點之間的內容做爲一個模板,而且經過循環屢次渲染該模板以後把結果填充到合適的位置。

  855. 在處理上面的那個模板時,須要不斷地建立新 scope 的,而且 o 這個成員須要單獨賦值。

  856. 注意:這裏只是簡單實現功能。官方的那個 ngRepeat 比較複雜,是作了專門的算法優化的。固然,這裏的實現也能夠是簡單把 DOM 結構變成使用 ngRepeat 的形式 :)

  857. JS 部分代碼:

  858.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('for', function($compile){ 4     var compile = function($element, $attrs, $link){ 5       var match = $element[0].outerHTML.match('<for (.*?)=.*? in=.*? (.*?)=.*?>'); 6       if(!match || match.length != 3){throw Error('syntax: <for o in obj_list>')} 7       var iter = match[1]; 8       var list = match[2]; 9       var tpl = $compile($.trim($element.html()));10       $element.empty();11   12       var link = function($scope, $ielement, $iattrs, $controller){13   14         var new_node = [];15   16         $scope.$watch(list, function(list){17           angular.forEach(new_node, function(n){n.remove()});18           var scp, inode;19           for(var i = 0, ii = list.length; i < ii; i++){20             scp = $scope.$new();21             scp[iter] = list[i];22             inode = tpl(scp, angular.noop);23             $ielement.before(inode);24             new_node.push(inode);25           }26   27         });28       }29   30       return link;31     }32     return {compile: compile,33             priority: 1000,34             terminal: true,35             restrict: 'E'};36   });37   38   app.controller('TestCtrl', angular.noop);39   angular.bootstrap(document, ['Demo']);
  859. 18.14. 示例:模板控制語句 if/else

  860. 這個示例是嘗試實現:

  861.   <div ng-controller="TestCtrl">
        <if true="a == 1">
            <p>判斷爲真, {{ name }}</p>
          <else>
            <p>判斷爲假, {{ name }}</p>
          </else>
        </if>
      
        <div>
          <p>a: <input ng-model="a" /></p>
          <p>name: <input ng-model="name" /></p>
        </div>
      </div>
  862. 考慮實現的思路:

  863. else 與 if 是兩個指令,它們是父子關係。經過 scope 能夠聯繫起來。至於 scope 是在link 中處理仍是 controller 中處理並不重要。

  864. true 屬性的條件判斷經過 $parse 服務很容易實現。

  865. 若是最終效果要去掉 if 節點,咱們可使用註釋節點來「佔位」。

  866. JS 代碼:

  867.  1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('if', function($parse, $compile){ 4     var compile = function($element, $attrs){ 5       var cond = $parse($attrs.true); 6        7       var link = function($scope, $ielement, $iattrs, $controller){ 8         $scope.if_node = $compile($.trim($ielement.html()))($scope, angular.noop); 9         $ielement.empty();10         var mark = $('<!-- IF/ELSE -->');11         $element.before(mark);12         $element.remove();13   14         $scope.$watch(function(scope){15           if(cond(scope)){16             mark.after($scope.if_node);17             $scope.else_node.detach();18           } else {19             if($scope.else_node !== undefined){20               mark.after($scope.else_node);21               $scope.if_node.detach();22             }23           }24         });25       }26       return link;27     }28   29     return {compile: compile,30             scope: true,31             restrict: 'E'}32   });33   34   app.directive('else', function($compile){35     var compile = function($element, $attrs){36       37       var link = function($scope, $ielement, $iattrs, $controller){38         $scope.else_node = $compile($.trim($ielement.html()))($scope, angular.noop);39         $element.remove();40       }41       return link;42     }43   44     return {compile: compile,45             restrict: 'E'}46   });47   48   app.controller('TestCtrl', function($scope){49     $scope.a = 1;50   });51   52   angular.bootstrap(document, ['Demo']);
  868. 代碼中注意一點,就是 if_node 在獲得之時,就已是作了變量綁定的了。錯誤的思路是,在$watch 中再去不斷地獲得新的 if_node 。

  869. 評論

  870. ©2010-2014 zouyesheng.com All rights reserved. Powered by GitHub , txt2tags , MathJax

  871. 開始的例子

  872. 依賴注入

  873. 做用域

  874. 數據綁定與模板

  875. 模板

  876. 模板中的過濾器

  877. 錨點路由

  878. 定義模板變量標識標籤

  879. AJAX

  880. 工具函數

  881. 其它服務

  882. 自定義模塊和服務

  883. 附加模塊 ngResource

  884. AngularJS與其它框架的混用(jQuery, Dojo)

  885. 自定義過濾器

  886. 自定義指令directive

本文的內容是在 1.0.x 版本之下完成的。

1. 關於AngularJS

AngularJS 是 Google 開源出來的一套 js 工具。下面簡稱其爲 ng 。這裏只說它是「工具」,沒說它是完整的「框架」,是由於它並非定位於去完成一套框架要作的事。更重要的,是它給咱們揭示了一種新的應用組織與開發方式。

ng 最讓我稱奇的,是它的數據雙向綁定。其實想一想,咱們一直在提數據與表現的分離,可是這裏的「雙向綁定」從某方面來講,是把數據與表現徹底綁定在一塊兒——數據變化,表現也變化。反之,表現變化了,內在的數據也變化。有過開發經驗的人能體會到這種機制對於前端應用來講,是頗有必要的,能帶來維護上的巨大優點。固然,這裏的綁定與提倡的分離並非矛盾的。

ng 能夠和 jQuery 集成工做,事實上,若是沒有 jQuery , ng 本身也作了一個輕量級的 jQuery ,主要實現了元素操做部分的 API 。

關於 ng 的幾點:

  • 對 IE 方面,它兼容 IE8 及以上的版本。

  • 與 jQuery 集成工做,它的一些對象與 jQuery 相關對象表現是一致的。

  • 使用 ng 時不要冒然去改變相關 DOM 的結構。

2. 關於本文檔

這份文檔如其名,是我本身學習 ng 的過程記錄。只是過程記錄,沒有刻意像教程那樣去作。因此呢,從前至後,中間難免有一些概念不清不明的地方。由於事實上,在某個階段對於一些概念原本就不可能明白。因此,整個過程只求在形式上的能用便可——直到最後的「自定義」那幾章,特別是「自定義指令」,那幾章過完,你才能看清 ng 原本的面貌。前面就不要太糾結概念,本質,知道怎麼用就好。

3. 開始的例子

咱們從一個完整的例子開始認識 ng :

 1   <!DOCTYPE html> 2   <html> 3   <head> 4   <meta charset="utf-8" /> 5    6   <title>試驗</title> 7    8   <script type="text/javascript" src="jquery-1.8.3.js"></script> 9   <script type="text/javascript" src="angular.js"></script>10   11   </head>12   <body>13     <div ng-controller="BoxCtrl">14       <div style="width: 100px; height: 100px; background-color: red;"15            ng-click="click()"></div>16       <p>{{ w }} x {{ h }}</p>17       <p>W: <input type="text" ng-model="w" /></p>18       <p>H: <input type="text" ng-model="h" /></p>19     </div>20   21   22   <script type="text/javascript" charset="utf-8">23   24   25   var BoxCtrl = function($scope, $element){26   27     //$element 就是一個 jQuery 對象28     var e = $element.children().eq(0);29     $scope.w = e.width();30     $scope.h = e.height();31   32     $scope.click = function(){33       $scope.w = parseInt($scope.w) + 10;34       $scope.h = parseInt($scope.h) + 10;35     }36   37     $scope.$watch('w',38       function(to, from){39         e.width(to);40       }41     );42   43     $scope.$watch('h',44       function(to, from){45         e.height(to);46       }47     );48   }49   50   angular.bootstrap(document.documentElement);51   </script>52   </body>53   </html>

從上面的代碼中,咱們看到在一般的 HTML 代碼當中,引入了一些標記,這些就是 ng 的模板機制,它不光完成數據渲染的工做,還實現了數據綁定的功能。

同時,在 HTML 中的自己的 DOM 層級結構,被 ng 利用起來,直接做爲它的內部機制中,上下文結構的判斷依據。好比例子中 p 是 div 的子節點,那麼 p 中的那些模板標記就是在 div 的 Ctrl 的做用範圍以內。

其它的,也一樣寫一些 js 代碼,裏面重要的是做一些數據的操做,事件的綁定定義等。這樣,數據的變化就會和頁面中的 DOM 表現聯繫起來。一旦這種聯繫創建起來,也即完成了咱們所說的「雙向綁定」。而後,這裏說的「事件」,除了那些「點擊」等一般的 DOM 事件以外,咱們還更關注「數據變化」這個事件。

最後,可使用:

  angular.bootstrap(document.documentElement);

來把整個頁面驅動起來了。(你能夠看到一個可被控制大小的紅色方塊)

更完整的方法是定義一個 APP :

 1   <!DOCTYPE html> 2   <html ng-app="MyApp"> 3   <head> 4   <meta charset="utf-8" /> 5    6   <title>數據正向綁定</title> 7    8   <script type="text/javascript" src="jquery-1.8.3.js"></script> 9   <script type="text/javascript" src="angular.js"></script>10   11   </head>12   <body>13   14   <div ng-controller="TestCtrl">15     <input type="text" value="" id="a" />16   </div>17   18   19   <script type="text/javascript">20   var TestCtrl = function(){21     console.log('ok');22   }23   24   //angular.bootstrap(document.documentElement);25   angular.module('MyApp', [], function(){console.log('here')});26   </script>27   28   </body>29   </html>

這裏說的一個 App 就是 ng 概念中的一個 Module 。對於 Controller 來講, 若是不想使用全局函數,也能夠在 app 中定義:

  var app = angular.module('MyApp', [], function(){console.log('here')});
  app.controller('TestCtrl',    function($scope){
      console.log('ok');
    }
  );

上面咱們使用 ng-app 來指明要使用的 App ,這樣的話能夠把顯式的初始化工做省了。通常完整的過程是:

  var app = angular.module('Demo', [], angular.noop);
  angular.bootstrap(document, ['Demo']);

使用 angular.bootstrap 來顯示地作初始化工具,參數指明瞭根節點,裝載的模塊(能夠是多個模塊)。

4. 依賴注入

injector , 我從 ng 的文檔中得知這個概念,以後去翻看源碼時瞭解了一下這個機制的工做原理。感受就是雖然與本身的所想僅差那麼一點點,但就是這麼一點點,讓我感慨想象力之神奇。

先看咱們以前代碼中的一處函數定義:

  var BoxCtrl = function($scope, $element){}

在這個函數定義中,注意那兩個參數: $scope , $element ,這是兩個頗有意思的東西。總的來講,它們是參數,這沒什麼可說的。但又不只僅是參數——你換個名字代碼就不能正常運行了。

事實上,這兩個參數,除了完成「參數」的自己任務以外,還做爲一種語法糖完成了「依賴聲明」的任務。原本這個函數定義,完整的寫法應該像 AMD 聲明同樣,寫成:

  var BoxCtrl = ['$scope', '$element', function(s, e){}];

這樣就很明顯,表示有一個函數,它依賴於兩個東西,而後這兩個東西會依次做爲參數傳入。

簡單起見,就寫成了一個函數定義本來的樣子,而後在定義參數的名字上做文章,來起到依賴聲明的做用。

在處理時,經過函數對象的 toString() 方法能夠知道這個函數定義代碼的字符串表現形式,而後就知道它的參數是 $scope 和 $element 。經過名字判斷出這是兩個外部依賴,而後就去獲取資源,最後把資源做爲參數,調用定義的函數。

因此,參數的名字是不能隨便寫的,這裏也充分利用了 js 的特色來儘可能作到「檢討」了。

在 Python 中受限於函數名的命名規則,寫出來不太好看。不過也得利於檢討機制,作到這點也很容易:

  # -*- coding: utf-8 -*-
  
  def f(Ia, Ib):      print Ia, Ib
  
  args = f.func_code.co_varnames
  SRV_MAP = {      'Ia': '123',      'Ib': '456',
  }
  
  srv = {}  for a in args:      if a in SRV_MAP:
          srv[a] = SRV_MAP[a]
  f(**srv)

5. 做用域

這裏提到的「做用域」的概念,是一個在範圍上與 DOM 結構一致,數據上相對於某個 $scope 對象的屬性的概念。咱們仍是從 HTML 代碼上來入手:

  <div ng-controller="BoxCtrl">
    <div style="width: 100px; height: 100px; background-color: red;"
         ng-click="click()">
    </div>
    <p>{{ w }} x {{ h }}</p>
    <p>W: <input type="text" ng-model="w" /></p>
    <p>H: <input type="text" ng-model="h" /></p>
  </div>

上面的代碼中,咱們給一個 div 元素指定了一個 BoxCtrl ,那麼, div 元素以內,就是 BoxCtrl 這個函數運行時, $scope 這個注入資源的控制範圍。在代碼中咱們看到的 click() , w , h 這些東西,它們原本的位置對應於 $scope.click , $scope.w , $scope.h 。

咱們在後面的 js 代碼中,也能夠看到咱們就是在操做這些變量。依賴於 ng 的數據綁定機制,操做變量的結果直接在頁面上表現出來了。

6. 數據綁定與模板

我糾結了半天,「數據綁定」與「模板」這兩個東西還真沒辦法分開來講。由於數據綁定須要以模板爲載體,離開了模板,數據還綁個毛啊。

ng 的一大特色,就是數據雙向綁定。雙向綁定是一體,爲了描述方便,下面分別介紹。

6.1. 數據->模板

數據到表現的綁定,主要是使用模板標記直接完成的:

  <p>{{ w }} x {{ h }}</p>

使用 {{ }} 這個標記,就能夠直接引用,並綁定一個做用域內的變量。在實現上, ng 自動建立了一個 watcher 。效果就是,無論由於什麼,若是做用域的變量發生了改變,咱們隨時可讓相應的頁面表現也隨之改變。咱們能夠看一個更純粹的例子:

  <p id="test" ng-controller="TestCtrl">{{ a }}</p>
  
  <script type="text/javascript">
  var TestCtrl = function($scope){
    $scope.a = '123';
  }
  angular.bootstrap(document.documentElement);

上面的例子在頁面載入以後,咱們能夠在頁面上看到 123 。這時,咱們能夠打開一個終端控制器,輸入:

  $('#test').scope().a = '12345';
  $('#test').scope().$digest();

上面的代碼執行以後,就能夠看到頁面變化了。

對於使用 ng 進行的事件綁定,在處理函數中就不須要去關心 $digest() 的調用了。由於 ng 會本身處理。源碼中,對於 ng 的事件綁定,真正的處理函數不是指定名字的函數,而是通過 $apply() 包裝過的一個函數。這個 $apply() 作的一件事,就是調用根做用域 $rootScope 的 $digest() ,這樣整個世界就清淨了:

  <p id="test" ng-controller="TestCtrl" ng-click="click()">{{ a }}</p>
  
  <script type="text/javascript" charset="utf-8">
  var TestCtrl = function($scope){
    $scope.a = '123';
  
    $scope.click = function(){
      $scope.a = '456';
    }
  }
  angular.bootstrap(document.documentElement);

那個 click 函數的定義,綁定時變成了相似於:

  function(){
    $scope.$apply(
      function(){
        $scope.click();
      }
    )
  }

這裏的 $scope.$apply() 中作的一件事:

  $rootScope.$digest();

6.2. 模板->數據

模板到數據的綁定,主要是經過 ng-model 來完成的:

  <input type="text" id="test" ng-controller="TestCtrl" ng-model="a" />
  
  <script type="text/javascript" charset="utf-8">
  var TestCtrl = function($scope){
    $scope.a = '123';
  }

這時修改 input 中的值,而後再在控制終端中使用:

  $('#test').scope().a

查看,發現變量 a 的值已經更改了。

實際上, ng-model 是把兩個方向的綁定都作了。它不光顯示出變量的值,也把顯示上的數值變化反映給了變量。這個在實現上就簡單多了,只是綁定 change 事件,而後作一些賦值操做便可。不過 ng 裏,還要區分對待不一樣的控件。

6.3. 數據->模板->數據->模板

如今要考慮的是一種在現實中很廣泛的一個需求。好比就是咱們能夠輸入數值,來控制一個矩形的長度。在這裏,數據與表現的關係是:

  • 長度數值保存在變量中

  • 變量顯示於某個 input 中

  • 變量的值便是矩形的長度

  • input 中的值變化時,變量也要變化

  • input 中的值變化時,矩形的長度也要變化

固然,要實現目的在這裏可能就不止一種方案了。按照之前的作法,很天然地會想法,綁定 input的 change 事件,而後去作一些事就行了。可是,咱們前面提到過 ng-model 這個東西,利用它就能夠在不手工處理 change 的條件下完成數據的展示需求,在此基礎之上,咱們還須要作的一點,就是把變化後的數據應用到矩形的長度之上。

最開始,咱們面對的應該是這樣一個東西:

  <div ng-controller="TestCtrl">
    <div style="width: 100px; height: 10px; background-color: red"></div>
    <input type="text" name="width" ng-model="width" />
  </div>
  
  <script type="text/javascript" charset="utf-8">  var TestCtrl = function($scope){
    $scope.width = 100;
  }
  angular.bootstrap(document.documentElement);
  </script>

咱們從響應數據變化,但又不使用 change 事件的角度來看,能夠這樣處理寬度變化:

  var TestCtrl = function($scope, $element){
    $scope.width = 100;
    $scope.$watch('width',      function(to, from){
        $element.children(':first').width(to);
      }
    );
  }

使用 $watch() 來綁定數據變化。

固然,這種樣式的問題,有更直接有效的手段, ng 的數據綁定老是讓人驚異:

  <div ng-controller="TestCtrl">
    <div style="width: 10px; height: 10px; background-color: red" ng-style="style">
    </div>
    <input type="text" name="width" ng-model="style.width" />
  </div>
  
  
  <script type="text/javascript" charset="utf-8">  var TestCtrl = function($scope){
    $scope.style = {width: 100 + 'px'};
  }
  angular.bootstrap(document.documentElement);
  </script>

7. 模板

前面講了數據綁定以後,如今能夠單獨講講模板了。

做爲一套能稱之謂「模板」的系統,除了能幹一些模板的常規的事以外(好吧,即便是常規的邏輯判斷如今它也作不了的),配合做用域 $scope 和 ng 的數據雙向綁定機制, ng 的模板系統就變得比較神奇了。

7.1. 定義模板內容

定義模板的內容如今有三種方式:

  1. 在須要的地方直接寫字符串

  2. 外部文件

  3. 使用 script 標籤訂義的「內部文件」

第一種不須要多說。第二種和第三種均可以和 ng-include 一塊兒工做,來引入一段模板。

直接引入同域的外部文件做爲模板的一部分:

  <div ng-include src="'tpl.html'">
  </div>
  
  <div ng-include="'tpl.html'">
  </div>

注意, src 中的字符串會做爲表達式處理(能夠是 $scope 中的變量),因此,直接寫名字的話須要使用引號。

引入 script 定義的「內部文件」:

  <script type="text/ng-template" id="tpl">
  here, {{ 1 + 1 }}
  </script>
  
  <div ng-include src="'tpl'"></div>

配合變量使用:

  <script type="text/ng-template" id="tpl">
  here, {{ 1 + 1 }}
  </script>
  
  <a ng-click="v='tpl'">Load</a>
  <div ng-include src="v"></div>

7.2. 內容渲染控制

7.2.1. 重複 ng-repeat

這算是惟一的一個控制標籤麼……,它的使用方法類型於:

  <div ng-controller="TestCtrl">
    <ul ng-repeat="member in obj_list">
      <li>{{ member }}</li>
    </ul>
  </div>
  
  
  var TestCtrl = function($scope){
    $scope.obj_list = [1,2,3,4];
  }

除此以外,它還提供了幾個變量可供使用:

  • $index 當前索引

  • $first 是否爲頭元素

  • $middle 是否爲非頭非尾元素

  • $last 是否爲尾元素

  <div ng-controller="TestCtrl">
    <ul ng-repeat="member in obj_list">
      <li>{{ $index }}, {{ member.name }}</li>
    </ul>
  </div>
  
  var TestCtrl = function($scope){
    $scope.obj_list = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
  }

7.2.2. 賦值 ng-init

這個指令能夠在模板中直接賦值,它做用於 angular.bootstrap 以前,而且,定義的變量與 $scope做用域無關。

  <div ng-controller="TestCtrl" ng-init="a=[1,2,3,4];">
    <ul ng-repeat="member in a">
      <li>{{ member }}</li>
    </ul>
  </div>

7.3. 節點控制

7.3.1. 樣式 ng-style

可使用一個結構直接表示當前節點的樣式:

  <div ng-style="{width: 100 + 'px', height: 100 + 'px', backgroundColor: 'red'}">
  </div>

一樣地,綁定一個變量的話,威力大了。

7.3.2. 類 ng-class

就是直接地設置當前節點的類,一樣,配合數據綁定做用就大了:

  <div ng-controller="TestCtrl" ng-class="cls">
  </div>

ng-class-even 和 ng-class-odd 是和 ng-repeat 配合使用的:

  <ul ng-init="l=[1,2,3,4]">
    <li ng-class-odd="'odd'" ng-class-even="'even'" ng-repeat="m in l">{{ m }}</li>
  </ul>

注意裏面給的仍是表示式,別少了引號。

7.3.3. 顯示和隱藏 ng-show ng-hide ng-switch

前兩個是控制 display 的指令:

  <div ng-show="true">1</div>
  <div ng-show="false">2</div>
  <div ng-hide="true">3</div>
  <div ng-hide="false">4</div>

後一個 ng-switch 是根據一個值來決定哪一個節點顯示,其它節點移除:

  <div ng-init="a=2">
    <ul ng-switch on="a">
      <li ng-switch-when="1">1</li>
      <li ng-switch-when="2">2</li>
      <li ng-switch-default>other</li>
    </ul>
  </div>

7.3.4. 其它屬性控制

ng-src 控制 src 屬性:

  <img ng-src="{{ 'h' + 'ead.png' }}" />

ng-href 控制 href 屬性:

  <a ng-href="{{ '#' + '123' }}">here</a>

總的來講:

  • ng-src src屬性

  • ng-href href屬性

  • ng-checked 選中狀態

  • ng-selected 被選擇狀態

  • ng-disabled 禁用狀態

  • ng-multiple 多選狀態

  • ng-readonly 只讀狀態

注意: 上面的這些只是單向綁定,即只是從數據到展現,不能副作用於數據。要雙向綁定,仍是要使用 ng-model 。

7.4. 事件綁定

事件綁定是模板指令中很好用的一部分。咱們能夠把相關事件的處理函數直接寫在 DOM 中,這樣作的最大好處就是能夠從 DOM 結構上看出業務處理的形式,你知道當你點擊這個節點時哪一個函數被執行了。

  • ng-change

  • ng-click

  • ng-dblclick

  • ng-mousedown

  • ng-mouseenter

  • ng-mouseleave

  • ng-mousemove

  • ng-mouseover

  • ng-mouseup

  • ng-submit

對於事件對象自己,在函數調用時能夠直接使用 $event 進行傳遞:

  <p ng-click="click($event)">點擊</p>
  <p ng-click="click($event.target)">點擊</p>

7.5. 表單控件

表單控件類的模板指令,最大的做用是它預約義了須要綁定的數據的格式。這樣,就能夠對於既定的數據進行既定的處理。

7.5.1. form

form 是核心的一個控件。 ng 對 form 這個標籤做了包裝。事實上, ng 本身的指令是叫 ng-form的,區別在於, form 標籤不能嵌套,而使用 ng-form 指令就能夠作嵌套的表單了。

form 的行爲中依賴它裏面的各個輸入控制的狀態的,在這裏,咱們主要關心的是 form 本身的一些方法和屬性。從 ng 的角度來講, form 標籤,是一個模板指令,也建立了一個 FormController 的實例。這個實例就提供了相應的屬性和方法。同時,它裏面的控件也是一個 NgModelController 實例。

很重要的一點, form 的相關方法要生效,必須爲 form 標籤指定 name 和 ng-controller ,而且每一個控件都要綁定一個變量。 form 和控件的名字,便是 $scope 中的相關實例的引用變量名。

  <form name="test_form" ng-controller="TestCtrl">
    <input type="text" name="a" required ng-model="a"  />
    <span ng-click="see()">{{ test_form.$valid }}</span>
  </form>
  
  var TestCtrl = function($scope){
  
    $scope.see = function(){
      console.log($scope.test_form);
      console.log($scope.test_form.a);
    }
  
  }

除去對象的方法與屬性, form 這個標籤自己有一些動態類可使用:

  • ng-valid 當表單驗證經過時的設置

  • ng-invalid 當表單驗證失敗時的設置

  • ng-pristine 表單的未被動以前擁有

  • ng-dirty 表單被動過以後擁有

form 對象的屬性有:

  • $pristine 表單是否未被動過

  • $dirty 表單是否被動過

  • $valid 表單是否驗證經過

  • $invalid 表單是否驗證失敗

  • $error 表單的驗證錯誤

其中的 $error 對象包含有全部字段的驗證信息,及對相關字段的 NgModelController 實例的引用。它的結構是一個對象, key 是失敗信息, required , minlength 之類的, value 是對應的字段實例列表。

注意,這裏的失敗信息是按序列取的一個。好比,若是一個字段既要求 required ,也要求minlength ,那麼當它爲空時, $error 中只有 required 的失敗信息。只輸入一個字符以後,required 條件知足了,纔可能有 minlength 這個失敗信息。

  <form name="test_form" ng-controller="TestCtrl">
    <input type="text" name="a" required ng-model="a"  />
    <input type="text" name="b" required ng-model="b" ng-minlength="2" />
    <span ng-click="see()">{{ test_form.$error }}</span>
  </form>
  
  var TestCtrl = function($scope){
    $scope.see = function(){
      console.log($scope.test_form.$error);
    }
  }

7.5.2. input

input 是數據的最主要入口。 ng 支持 HTML5 中的相關屬性,同時對舊瀏覽器也作了兼容性處理。最重要的, input 的規則定義,是所屬表單的相關行爲的參照(好比表單是否驗證成功)。

input 控件的相關可用屬性爲:

  • name 名字

  • ng-model 綁定的數據

  • required 是否必填

  • ng-required 是否必填

  • ng-minlength 最小長度

  • ng-maxlength 最大長度

  • ng-pattern 匹配模式

  • ng-change 值變化時的回調

  <form name="test_form" ng-controller="TestCtrl">
    <input type="text" name="a" ng-model="a" required ng-pattern="/abc/" />
    <span ng-click="see()">{{ test_form.$error }}</span>
  </form>

input 控件,它還有一些擴展,這些擴展有些有本身的屬性:

  • input type="number" 多了 number 錯誤類型,多了 max , min 屬性。

  • input type="url" 多了 url 錯誤類型。

  • input type="email" 多了 email 錯誤類型。

7.5.3. checkbox

它也算是 input 的擴展,不過,它沒有驗證相關的東西,只有選中與不選中兩個值:

  <form name="test_form" ng-controller="TestCtrl">
    <input type="checkbox" name="a" ng-model="a" ng-true-value="AA" ng-false-value="BB" />
    <span>{{ a }}</span>
  </form>
  
  var TestCtrl = function($scope){
    $scope.a = 'AA';
  }

兩點:

  1. controller 要初始化變量值。

  2. controller 中的初始化值會關係到控件狀態(雙向綁定)。

7.5.4. radio

也是 input 的擴展。和 checkbox 同樣,但它只有一個值了:

  <form name="test_form" ng-controller="TestCtrl">
    <input type="radio" name="a" ng-model="a" value="AA" />
    <input type="radio" name="a" ng-model="a" value="BB" />
    <span>{{ a }}</span>
  </form>

7.5.5. textarea

同 input 。

7.5.6. select

這是一個比較牛B的控件。它裏面的一個叫作 ng-options 的屬性用於數據呈現。

對於給定列表時的使用。

最簡單的使用方法, x for x in list :

  <form name="test_form" ng-controller="TestCtrl" ng-init="o=[0,1,2,3]; a=o[1];">
    <select ng-model="a" ng-options="x for x in o" ng-change="show()">
      <option value="">能夠加這個空值</option>
    </select>
  </form>
  
  <script type="text/javascript">  var TestCtrl = function($scope){
    $scope.show = function(){
      console.log($scope.a);
    }
  }
  
  angular.bootstrap(document.documentElement);
  </script>

在 $scope 中, select 綁定的變量,其值和普通的 value 無關,能夠是一個對象:

  <form name="test_form" ng-controller="TestCtrl"
        ng-init="o=[{name: 'AA'}, {name: 'BB'}]; a=o[1];">
    <select ng-model="a" ng-options="x.name for x in o" ng-change="show()">
    </select>
  </form>

顯示與值分別指定, x.v as x.name for x in o :

  <form name="test_form" ng-controller="TestCtrl"
        ng-init="o=[{name: 'AA', v: '00'}, {name: 'BB', v: '11'}]; a=o[1].v;">
    <select ng-model="a" ng-options="x.v as x.name for x in o" ng-change="show()">
    </select>
  </form>

加入分組的, x.name group by x.g for x in o :

  <form name="test_form" ng-controller="TestCtrl"
        ng-init="o=[{name: 'AA', g: '00'}, {name: 'BB', g: '11'}, {name: 'CC', g: '00'}]; a=o[1];">
    <select ng-model="a" ng-options="x.name group by x.g for x in o" ng-change="show()">
    </select>
  </form>

分組了還分別指定顯示與值的, x.v as x.name group by x.g for x in o :

  <form name="test_form" ng-controller="TestCtrl" ng-init="o=[{name: 'AA', g: '00', v: '='}, {name: 'BB', g: '11', v: '+'}, {name: 'CC', g: '00', v: '!'}]; a=o[1].v;">
    <select ng-model="a" ng-options="x.v as x.name group by x.g for x in o" ng-change="show()">
    </select>
  </form>

若是參數是對象的話,基本也是同樣的,只是把遍歷的對象改爲 (key, value) :

  <form name="test_form" ng-controller="TestCtrl" ng-init="o={a: 0, b: 1}; a=o.a;">
    <select ng-model="a" ng-options="k for (k, v) in o" ng-change="show()">
    </select>
  </form>
  
  <form name="test_form" ng-controller="TestCtrl"
        ng-init="o={a: {name: 'AA', v: '00'}, b: {name: 'BB', v: '11'}}; a=o.a.v;">
    <select ng-model="a" ng-options="v.v as v.name for (k, v) in o" ng-change="show()">
    </select>
  </form>
  
  <form name="test_form" ng-controller="TestCtrl"
        ng-init="o={a: {name: 'AA', v: '00', g: '=='}, b: {name: 'BB', v: '11', g: '=='}}; a=o.a;">
    <select ng-model="a" ng-options="v.name group by v.g for (k, v) in o" ng-change="show()">
    </select>
  </form>
  
  <form name="test_form" ng-controller="TestCtrl"
        ng-init="o={a: {name: 'AA', v: '00', g: '=='}, b: {name: 'BB', v: '11', g: '=='}}; a=o.a.v;">
    <select ng-model="a" ng-options="v.v as v.name group by v.g for (k, v) in o" ng-change="show()">
    </select>
  </form>

8. 模板中的過濾器

這裏說的過濾器,是用於對數據的格式化,或者篩選的函數。它們能夠直接在模板中經過一種語法使用。對於經常使用功能來講,是很方便的一種機制。

多個過濾器之間能夠直接連續使用。

8.1. 排序 orderBy

orderBy 是一個排序用的過濾器標籤。它能夠像 sort 函數那樣支持一個排序函數,也能夠簡單地指定一個屬性名進行操做:

  <div ng-controller="TestCtrl">
    {{ data | orderBy: 'age' }} <br />
    {{ data | orderBy: '-age' }} <br />
    {{ data | orderBy: '-age' | limitTo: 2 }} <br />
    {{ data | orderBy: ['-age', 'name'] }} <br />
  </div>
  
  
  <script type="text/javascript">  var TestCtrl = function($scope){
    $scope.data = [
      {name: 'B', age: 4},  
      {name: 'A', age: 1},  
      {name: 'D', age: 3},  
      {name: 'C', age: 3},  
    ];
  }
  
  angular.bootstrap(document.documentElement);
  </script>

8.2. 過濾列表 filter

filter 是一個過濾內容的標籤。

若是參數是一個字符串,則列表成員中的任意屬性值中有這個字符串,即爲知足條件(忽略大小寫):

  <div ng-controller="TestCtrl">
    {{ data | filter: 'b' }} <br />
    {{ data | filter: '!B' }} <br />
  </div>
  
  
  <script type="text/javascript">  var TestCtrl = function($scope){
    $scope.data = [
      {name: 'B', age: 4},  
      {name: 'A', age: 1},  
      {name: 'D', age: 3},  
      {name: 'C', age: 3},  
    ];
  }
  
  angular.bootstrap(document.documentElement);
  </script>

可使用對象,來指定屬性名, $ 表示任意屬性:

  {{ data | filter: {name: 'A'} }} <br />
  {{ data | filter: {$: '3'} }} <br />
  {{ data | filter: {$: '!3'} }} <br />

自定義的過濾函數也支持:

  <div ng-controller="TestCtrl">
    {{ data | filter: f }} <br />
  </div>
  
  
  <script type="text/javascript">  var TestCtrl = function($scope){
    $scope.data = [
      {name: 'B', age: 4},  
      {name: 'A', age: 1},  
      {name: 'D', age: 3},  
      {name: 'C', age: 3},  
    ];
  
    $scope.f = function(e){      return e.age > 2;
    }
  }
  
  angular.bootstrap(document.documentElement);
  </script>

8.3. 其它

時間戳格式化 date :

  <div ng-controller="TestCtrl">
  {{ a | date: 'yyyy-MM-dd HH:mm:ss' }}
  </div>
  
  <script type="text/javascript">  var TestCtrl = function($scope){
    $scope.a = ((new Date().valueOf()));
  }
  
  angular.bootstrap(document.documentElement);
  </script>

列表截取 limitTo ,支持正負數:

  {{ [1,2,3,4,5] | limitTo: 2 }}
  {{ [1,2,3,4,5] | limitTo: -3 }}

大小寫 lowercase , uppercase :

  {{ 'abc' | uppercase }}
  {{ 'Abc' | lowercase }}

8.4. 例子:表頭排序

 1   <div ng-controller="TestCtrl"> 2     <table> 3       <tr> 4         <th ng-click="f='name'; rev=!rev">名字</th> 5         <th ng-click="f='age'; rev=!rev">年齡</th> 6       </tr> 7    8       <tr ng-repeat="o in data | orderBy: f : rev"> 9         <td>{{ o.name }}</td>10         <td>{{ o.age }}</td>11       </tr>12     </table>13   </div>14   15   <script type="text/javascript">16   var TestCtrl = function($scope){17     $scope.data = [18       {name: 'B', age: 4},  
19       {name: 'A', age: 1},  
20       {name: 'D', age: 3},  
21       {name: 'C', age: 3},  
22     ];23   }24   25   angular.bootstrap(document.documentElement);26   </script>

8.5. 例子:搜索

  <div ng-controller="TestCtrl" ng-init="s=data[0].name; q=''">
    <div>
      <span>查找:</span> <input type="text" ng-model="q" />
    </div>
    <select ng-multiple="true" ng-model="s"
            ng-options="o.name as o.name + '(' + o.age + ')' for o in data | filter: {name: q} | orderBy: ['age', 'name'] ">
    </select>
  </div>
  
  <script type="text/javascript">  var TestCtrl = function($scope){
    $scope.data = [
      {name: 'B', age: 4},  
      {name: 'A', age: 1},  
      {name: 'D', age: 3},  
      {name: 'C', age: 3},  
    ];
  }
  
  angular.bootstrap(document.documentElement);
  </script>

9. 錨點路由

準確地說,這應該叫對 hashchange 事件的處理吧。

就是指 URL 中的錨點部分發生變化時,觸發預先定義的業務邏輯。好比如今是 /test#/x ,錨點部分的值爲 # 後的 /x ,它就對應了一組處理邏輯。當這部分變化時,好比變成了 /test#/t ,這時頁面是不會刷新的,可是它能夠觸發另一組處理邏輯,來作一些事,也可讓頁面發生變化。

這種機制對於複雜的單頁面來講,無疑是一種強大的業務切分手段。就算不是複雜的單頁面應用,在普通頁面上善用這種機制,也可讓業務邏輯更容易控制。

ng 提供了完善的錨點路由功能,雖然目前我以爲至關重要的一個功能還有待完善(後面會說),但目前這功能的幾部份內容,已經讓我思考了不少種可能性了。

ng 中的錨點路由功能是由幾部分 API 共同完成的一整套方案。這其中包括了路由定義,參數定義,業務處理等。

9.1. 路由定義

要使用錨點路由功能,須要在先定義它。目前,對於定義的方法,我我的只發如今「初始化」階段能夠經過 $routeProvider 這個服務來定義。

在定義一個 app 時能夠定義錨點路由:

  <html ng-app="ngView">
    ... ...
  
  <div ng-view></div>
  
  <script type="text/javascript">
  
  angular.module('ngView', [],
    function($routeProvider){
      $routeProvider.when('/test',
        {
          template: 'test',
        }
      );
    }
  );
  
  </script>

首先看 ng-view 這個 directive ,它是一個標記「錨點做用區」的指令。目前頁面上只能有一個「錨點做用區」。有人已經提了,「多個可命名」的錨點做用區的代碼到官方,可是目前官方尚未接受合併,我以爲多個做用區這個功能是很重要的,但願下個發佈版中能有。

錨點做用區的功能,就是讓錨點路由定義時的那些模板, controller 等,它們產生的 HTML 代碼放在做用區內。

好比上面的代碼,當你剛打開頁面時,頁面是空白的。你手動訪問 /#/test 就能夠看到頁面上出現了 'test' 的字樣。

在 angular.bootstrap() 時也能夠定義:

  angular.bootstrap(document.documentElement, [    function($routeProvider){
      $routeProvider.when('/test',
        {
          template: 'test'
        }
      );
    }
  ]);

9.2. 參數定義

在做路由定義時,能夠匹配一個規則,規則中能夠定義路徑中的某些部分做爲參數之用,而後使用$routeParams 服務獲取到指定參數。好比 /#/book/test 中, test 做爲參數傳入到 controller 中:

  <div ng-view></div>  
  
  <script type="text/javascript">
  
  angular.module('ngView', [],    function($routeProvider){
      $routeProvider.when('/book/:title',
        {
          template: '{{ title }}',
          controller: function($scope, $routeParams){
            $scope.title = $routeParams.title;
          }
        }
      );
    }
  );  
  </script>

訪問: /#/book/test

不須要預約義模式,也能夠像普通 GET 請求那樣獲取到相關參數:

  angular.module('ngView', [],    function($routeProvider){
      $routeProvider.when('/book',
        {
          template: '{{ title }}',
          controller: function($scope, $routeParams){
            $scope.title = $routeParams.title;
          }
        }
      );
    }
  );

訪問: /#/book?title=test

9.3. 業務處理

簡單來講,當一個錨點路由定義被匹配時,會根據模板生成一個 $scope ,同時相應的一個 controller 就會被觸發。最後模板的結果會被填充到 ng-view 中去。

從上面的例子中能夠看到,最直接的方式,咱們能夠在模板中雙向綁定數據,而數據的來源,在 controller 中控制。在 controller 中,又可使用到像 $scope , $routeParams 這些服務。

這裏先提一下另一種與錨點路由相關的服務, $route 。這個服務裏錨點路由在定義時,及匹配過程當中的信息。好比咱們搞怪一下:

  angular.module('ngView', [],    function($routeProvider){
      $routeProvider.when('/a',
        {
          template: '{{ title }}',
          controller: function($scope){
            $scope.title = 'a';
          }
        }
      );
  
      $routeProvider.when('/b',
        {
          template: '{{ title }}',
          controller: function($scope, $route){
            console.log($route);
            $route.routes['/a'].controller($scope);
          }
        }
      );
    }
  );

回到錨點定義的業務處理中來。咱們能夠以字符串形式寫模板,也能夠直接引用外部文件做爲模板:

  angular.module('ngView', [],
    function($routeProvider){
      $routeProvider.when('/test',
        {
          templateUrl: 'tpl.html',
          controller: function($scope){
            $scope.title = 'a';
          }
        }
      );
    }
  );

tpl.html 中的內容是:

  {{ title }}

這樣的話,模板能夠預約義,也能夠很複雜了。

如今暫時忘了模板吧,由於前面提到的,當前 ng-view 不能有多個的限制,模板的渲染機制侷限性仍是很大的。不過,反正會觸發一個 controller ,那麼在函數當中咱們能夠儘可能地幹本身喜歡的事:

  angular.module('ngView', [],
    function($routeProvider){
      $routeProvider.when('/test',
        {
          template: '{{}}',
          controller: function(){
            $('div').first().html('<b>OK</b>');
          }
        }
      );
    }
  );

那個空的 template 不能省,不然 controller 不會被觸發。

10. 定義模板變量標識標籤

因爲下面涉及動態內容,因此我打算起一個後端服務來作。可是我發現我使用的 Tornado 框架的模板系統,與 ng 的模板系統,都是使用 {{ }} 這對符號來定義模板表達式的,這太悲劇了,不過幸虧 ng 已經提供了修改方法:

  angular.bootstrap(document.documentElement,
    [function($interpolateProvider){
      $interpolateProvider.startSymbol('[[');
      $interpolateProvider.endSymbol(']]');
    }]);

使用 $interpolateProvider 服務便可。

11. AJAX

ng 提供了基本的 AJAX 封裝,你直接面對 promise 對象,使用起來仍是很方便的。

11.1. HTTP請求

基本的操做由 $http 服務提供。它的使用很簡單,提供一些描述請求的參數,請求就出去了,而後返回一個擴充了 success 方法和 error 方法的 promise 對象(下節介紹),你能夠在這個對象中添加須要的回調函數。

  var TestCtrl = function($scope, $http){    var p = $http({
      method: 'GET',
      url: '/json'
    });
    p.success(function(response, status, headers, config){
        $scope.name = response.name;
    });
  }

$http 接受的配置項有:

  • method 方法

  • url 路徑

  • params GET請求的參數

  • data post請求的參數

  • headers 頭

  • transformRequest 請求預處理函數

  • transformResponse 響應預處理函數

  • cache 緩存

  • timeout 超時毫秒,超時的請求會被取消

  • withCredentials 跨域安全策略的一個東西

其中的 transformRequest 和 transformResponse 及 headers 已經有定義的,若是自定義則會覆蓋默認定義:

 1   var $config = this.defaults = { 2     // transform incoming response data 3     transformResponse: [function(data) { 4       if (isString(data)) { 5         // strip json vulnerability protection prefix 6         data = data.replace(PROTECTION_PREFIX, ''); 7         if (JSON_START.test(data) && JSON_END.test(data)) 8           data = fromJson(data, true); 9       }10       return data;11     }],12   13     // transform outgoing request data14     transformRequest: [function(d) {15       return isObject(d) && !isFile(d) ? toJson(d) : d;16     }],17   18     // default headers19     headers: {20       common: {21         'Accept': 'application/json, text/plain, */*',22         'X-Requested-With': 'XMLHttpRequest'23       },24       post: {'Content-Type': 'application/json;charset=utf-8'},25       put:  {'Content-Type': 'application/json;charset=utf-8'}26     }27   };

注意它默認的 POST 方法出去的 Content-Type

對於幾個標準的 HTTP 方法,有對應的 shortcut :

  • $http.delete(url, config)

  • $http.get(url, config)

  • $http.head(url, config)

  • $http.jsonp(url, config)

  • $http.post(url, data, config)

  • $http.put(url, data, config)

注意其中的 JSONP 方法,在實現上會在頁面中添加一個 script 標籤,而後放出一個 GET 請求。你本身定義的,匿名回調函數,會被 ng 自已給一個全局變量。在定義請求,做爲 GET 參數,你可使用 JSON_CALLBACK 這個字符串來暫時代替回調函數名,以後 ng 會爲你替換成真正的函數名:

  var p = $http({
    method: 'JSONP',
    url: '/json',
    params: {callback: 'JSON_CALLBACK'}
  });
  p.success(function(response, status, headers, config){
      console.log(response);
      $scope.name = response.name;
  });

$http 有兩個屬性:

  • defaults 請求的全局配置

  • pendingRequests 當前的請求隊列狀態

  $http.defaults.transformRequest = function(data){console.log('here'); return data;}
  console.log($http.pendingRequests);

11.2. 廣義回調管理

和其它框架同樣, ng 提供了廣義的異步回調管理的機制。 $http 服務是在其之上封裝出來的。這個機制就是 ng 的 $q 服務。

不過 ng 的這套機制總的來講實現得比較簡單,按官方的說法,夠用了。

使用的方法,基本上是:

  • 經過 $q 服務獲得一個 deferred 實例

  • 經過 deferred 實例的 promise 屬性獲得一個 promise 對象

  • promise 對象負責定義回調函數

  • deferred 實例負責觸發回調

  var TestCtrl = function($q){    var defer = $q.defer();    var promise = defer.promise;
    promise.then(function(data){console.log('ok, ' + data)},                 function(data){console.log('error, ' + data)});    //defer.reject('xx');
    defer.resolve('xx');
  }

瞭解了上面的東西,再分別看 $q , deferred , promise 這三個東西。

11.2.1. $q

$q 有四個方法:

  • $q.all() 合併多個 promise ,獲得一個新的 promise

  • $q.defer() 返回一個 deferred 對象

  • $q.reject() 包裝一個錯誤,以使回調鏈能正確處理下去

  • $q.when() 返回一個 promise 對象

$q.all() 方法適用於併發場景很合適:

  var TestCtrl = function($q, $http){    var p = $http.get('/json', {params: {a: 1}});    var p2 = $http.get('/json', {params: {a: 2}});    var all = $q.all([p, p2]);
    p.success(function(res){console.log('here')});
    all.then(function(res){console.log(res[0])});
  }

$q.reject() 方法是在你捕捉異常以後,又要把這個異常在回調鏈中傳下去時使用:

要理解這東西,先看看 promise 的鏈式回調是如何運做的,看下面兩段代碼的區別:

  var defer = $q.defer();  var p = defer.promise;
  p.then(    function(data){return 'xxx'}
  );
  p.then(    function(data){console.log(data)}
  );
  defer.resolve('123');
  var defer = $q.defer();  var p = defer.promise;  var p2 = p.then(    function(data){return 'xxx'}
  );
  p2.then(    function(data){console.log(data)}
  );
  defer.resolve('123');

從模型上看,前者是「併發」,後者纔是「鏈式」。

而 $q.reject() 的做用就是觸發後鏈的 error 回調:

  var defer = $q.defer();  var p = defer.promise;
  p.then(    function(data){return data},    function(data){return $q.reject(data)}
  ).
  then(    function(data){console.log('ok, ' + data)},    function(data){console.log('error, ' + data)}
  )
  defer.reject('123');

最後的 $q.when() 是把數據封裝成 promise 對象:

  var p = $q.when(0, function(data){return data},                     function(data){return data});
  p.then(    function(data){console.log('ok, ' + data)},    function(data){console.log('error, ' + data)}
  );

11.2.2. deferred

deferred 對象有兩個方法一個屬性。

  • promise 屬性就是返回一個 promise 對象的。

  • resolve() 成功回調

  • reject() 失敗回調

  var defer = $q.defer();  var promise = defer.promise;
  promise.then(function(data){console.log('ok, ' + data)},               function(data){console.log('error, ' + data)});  //defer.reject('xx');
  defer.resolve('xx');

11.2.3. promise

promise 對象只有 then() 一個方法,註冊成功回調函數和失敗回調函數,再返回一個 promise 對象,以用於鏈式調用。

12. 工具函數

12.1. 上下文綁定

angular.bind 是用來進行上下文綁定,參數動態綁定的工具函數。

  var f = angular.bind({a: 'xx'},
    function(){
      console.log(this.a);
    }
  );
  f();

參數動態綁定:

  var f = function(x){console.log(x)}
  angular.bind({}, f, 'x')();

12.2. 對象處理

對象複製: angular.copy()

  var a = {'x': '123'};
  var b = angular.copy(a);
  a.x = '456';
  console.log(b);

對象聚合: angular.extend()

  var a = {'x': '123'};
  var b = {'xx': '456'};
  angular.extend(b, a);
  console.log(b);

空函數: angular.noop()

大小寫轉換: angular.lowercase() 和 angular.uppercase()

JSON轉換: angular.fromJson() 和 angular.toJson()

遍歷: angular.forEach() ,支持列表和對象:

  var l = {a: '1', b: '2'};
  angular.forEach(l, function(v, k){console.log(k + ': ' + v)});
  
  var l = ['a', 'b', 'c'];
  angular.forEach(l, function(v, i, o){console.log(v)});
  
  var context = {'t': 'xx'};
  angular.forEach(l, function(v, i, o){console.log(this.t)}, context);

12.3. 類型斷定

  • angular.isArray

  • angular.isDate

  • angular.isDefined

  • angular.isElement

  • angular.isFunction

  • angular.isNumber

  • angular.isObject

  • angular.isString

  • angular.isUndefined

13. 其它服務

13.1. 日誌

ng 提供 $log 這個服務用於向終端輸出相關信息:

  • error()

  • info()

  • log()

  • warn()

  var TestCtrl = function($log){
    $log.error('error');
    $log.info('info');
    $log.log('log');
    $log.warn('warn');
  }

13.2. 緩存

ng 提供了一個簡單封裝了緩存機制 $cacheFactory ,能夠用來做爲數據容器:

  var TestCtrl = function($scope, $cacheFactory){
    $scope.cache = $cacheFactory('s_' + $scope.$id, {capacity: 3});
  
    $scope.show = function(){
      console.log($scope.cache.get('a'));
      console.log($scope.cache.info());
    }
  
    $scope.set = function(){
      $scope.cache.put((new Date()).valueOf(), 'ok');
    }
  }

調用時,第一個參數是 id ,第二個參數是配置項,目前支持 capacity 參數,用以設置緩存能容留的最大條目數。超過這個個數,則自動清除較舊的條目。

緩存實例的方法:

  • info() 獲取 id , size 信息

  • put(k, v) 設置新條目

  • get(k) 獲取條目

  • remove(k) 刪除條目

  • removeAll() 刪除全部條目

  • destroy() 刪除對本實例的引用

$http 的調用當中,有一個 cache 參數,值爲 true 時爲自動維護的緩存。值也能夠設置爲一個 cache 實例。

13.3. 計時器

$timeout 服務是 ng 對 window.setTimeout() 的封裝,它使用 promise 統一了計時器的回調行爲:

  var TestCtrl = function($timeout){
    var p = $timeout(function(){console.log('haha')}, 5000);
    p.then(function(){console.log('x')});    //$timeout.cancel(p);
  }

使用 $timeout.cancel() 能夠取消計時器。

13.4. 表達式函數化

$parse 這個服務,爲 js 提供了相似於 Python 中 @property 的能力:

  var TestCtrl = function($scope, $parse){
    $scope.get_name = $parse('name');
    $scope.show = function(){console.log($scope.get_name($scope))}
    $scope.set = function(){$scope.name = '123'}
  }

$parse 返回一個函數,調用這個函數時,能夠傳兩個參數,第一個做用域,第二個是變量集,後者經常使用於覆蓋前者的變量:

  var get_name = $parse('name');  var r = get_name({name: 'xx'}, {name: 'abc'});
  console.log(r);

$parse 返回的函數,也提供了相應的 assign 功能,能夠爲表達式賦值(若是能夠的話):

  var get_name = $parse('name');  var set_name = get_name.assign;  var r = get_name({name: 'xx'}, {name: 'abc'});
  console.log(r);  
  var s = {}
  set_name(s, '123');  var r = get_name(s);
  console.log(r);

13.5. 模板單獨使用

ng 中的模板是很重要,也很強大的一個機制,天然少不了單獨運用它的方法。不過,即便是單獨使用,也是和 DOM 緊密相關的程度:

  • 定義時必須是有 HTML 標籤包裹的,這樣才能建立 DOM 節點

  • 渲染時必須傳入 $scope

以後使用 $compile 就能夠獲得一個渲染好的節點對象了。固然, $compile 還要作其它一些工做,指令處理什麼的。

  var TestCtrl = function($scope, $element,$compile){
    $scope.a = '123';
    $scope.set = function(){      var tpl = $compile('<p>hello {{ a }}</p>');      var e = tpl($scope);
      $element.append(e);
    }
  }

14. 自定義模塊和服務

14.1. 模塊和服務的概念與關係

總的來講,模塊是組織業務的一個框框,在一個模塊當中定義多個服務。當你引入了一個模塊的時候,就可使用這個模塊提供的一種或多種服務了。

好比 AngularJS 自己的一個默認模塊叫作 ng ,它提供了 $http , $q 等等服務。

服務只是模塊提供的多種機制中的一種,其它的還有命令( directive ),過濾器( filter ),及其它配置信息。

而後在額外的 js 文件中有一個附加的模塊叫作 ngResource , 它提供了一個 $resource 服務。

定義時,咱們能夠在已有的模塊中新定義一個服務,也能夠先新定義一個模塊,而後在新模塊中定義新服務。

使用時,模塊是須要顯式地的聲明依賴(引入)關係的,而服務則可讓 ng 自動地作注入,而後直接使用。

14.2. 定義模塊

定義模塊的方法是使用 angular.module 。調用時聲明瞭對其它模塊的依賴,並定義了「初始化」函數。

  var my_module = angular.module('MyModule', [], function(){
      console.log('here');
  });

這段代碼定義了一個叫作 MyModule 的模塊, my_module 這個引用能夠在接下來作其它的一些事,好比定義服務。

14.3. 定義服務

服務自己是一個任意的對象。可是 ng 提供服務的過程涉及它的依賴注入機制。在這裏呢,就要先介紹一下叫 provider 的東西。

簡單來講, provider 是被「注入控制器」使用的一個對象,注入機制經過調用一個 provider 的 $get()方法,把獲得的東西做爲參數進行相關調用(好比把獲得的服務做爲一個 Controller 的參數)。

在這裏「服務」的概念就比較不明確,對使用而言,服務僅指 $get() 方法返回的東西,可是在總體機制上,服務又要指提供了 $get() 方法的整個對象。

  //這是一個provider
  var pp = function(){    this.$get = function(){      return {'haha': '123'};
    }
  }  
  //我在模塊的初始化過程中, 定義了一個叫 PP 的服務
  var app = angular.module('Demo', [], function($provide){
    $provide.provider('PP', pp);
  });  
  //PP服務實際上就是 pp 這個 provider 的 $get() 方法返回的東西
  app.controller('TestCtrl',    function($scope, PP){
      console.log(PP);
    }
  );

上面的代碼是一種定義服務的方法,固然, ng 還有相關的 shortcut, ng 總有不少 shortcut 。

第一個是 factory 方法,由 $provide 提供, module 的 factory 是一個引用,做用同樣。這個方法直接把一個函數當成是一個對象的 $get() 方法,這樣你就不用顯式地定義一個 provider 了:

  var app = angular.module('Demo', [], function($provide){
    $provide.factory('PP', function(){      return {'hello': '123'};
    });
  });
  app.controller('TestCtrl', function($scope, PP){ console.log(PP) });

在 module 中使用:

  var app = angular.module('Demo', [], function(){ });
  app.factory('PP', function(){return {'abc': '123'}});
  app.controller('TestCtrl', function($scope, PP){ console.log(PP) });

第二個是 service 方法,也是由 $provide 提供, module 中有對它的同名引用。 service 和factory 的區別在於,前者是要求提供一個「構造方法」,後者是要求提供 $get() 方法。意思就是,前者必定是獲得一個 object ,後者能夠是一個數字或字符串。它們的關係大概是:

  var app = angular.module('Demo', [], function(){ });
  app.service = function(name, constructor){
    app.factory(name, function(){      return (new constructor());
    });
  }

這裏插一句,js 中 new 的做用,以 new a() 爲例,過程至關於:

  1. 建立一個空對象 obj

  2. 把 obj 綁定到 a 函數的上下文當中(即 a 中的 this 如今指向 obj )

  3. 執行 a 函數

  4. 返回 obj

service 方法的使用就很簡單了:

  var app = angular.module('Demo', [], function(){ });
  app.service('PP', function(){    this.abc = '123';
  });
  app.controller('TestCtrl', function($scope, PP){ console.log(PP) });

14.4. 引入模塊並使用服務

結合上面的「定義模塊」和「定義服務」,咱們能夠方便地組織本身的額外代碼:

  angular.module('MyModule', [], function($provide){
    $provide.factory('S1', function(){      return 'I am S1';
    });
    $provide.factory('S2', function(){      return {see: function(){return 'I am S2'}}
    });
  });  
  var app = angular.module('Demo', ['MyModule'], angular.noop);
  app.controller('TestCtrl', function($scope, S1, S2){
    console.log(S1)
    console.log(S2.see())
  });

15. 附加模塊 ngResource

15.1. 使用引入與總體概念

ngResource 這個是 ng 官方提供的一個附加模塊。附加的意思就是,若是你打算用它,那麼你須要引入一人單獨的 js 文件,而後在聲明「根模塊」時註明依賴的 ngResource 模塊,接着就可使用它提供的 $resource 服務了。完整的過程形如:

  <!DOCTYPE html>
  <html ng-app="Demo">
  <head>
  <meta charset="utf-8" />
  <title>AngularJS</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"></script>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular-resource.js"></script>
  </head>
  <body>
  
    <div ng-controller="TestCtrl"></div>
  
  
  <script type="text/javascript" charset="utf-8">  
  var app = angular.module('Demo', ['ngResource'], angular.noop);
  app.controller('TestCtrl', function($scope, $resource){
    console.log($resource);
  });
  
  </script>
  </body>
  </html>

$resource 服務,總體上來講,比較像是使用相似 ORM 的方式來包裝了 AJAX 調用。區別就是 ORM 是操做數據庫,即拼出 SQL 語句以後,做 execute 方法調用。而 $resource 的方式是構造出 AJAX 請求,而後發出請求。同時,AJAX 請求是須要回調處理的,這方面, $resource 的機制可使你在一些時候省掉回調處理,固然,是否做回調處理在於業務情形及容錯需求了。

使用上 $resource 分紅了「類」與「實例」這兩個層面。通常地,類的方法調用就是直觀的調用形式,一般會返回一個對象,這個對象即爲「實例」。

「實例」貫穿整個服務的使用過程。「實例」的數據是填充方式,即由於異步關係,回調函數沒有執行時,實例已經存在,只是可能它尚未相關數據,回調執行以後,相關數據被填充到實例對象當中。實例的方法通常就是在類方法名前加一個 $ ,調用上,根據定義,實例數據可能會作一些自動的參數填充,這點是區別實例與類的調用上的不一樣。

好吧,上面這些話可能須要在看了接下來的內容以後再回過來理解。

15.2. 基本定義

就像使用 ORM 通常要先定義 Model 同樣,使用 $resource 須要先定義「資源」,也就是先定義一些 HTTP 請求。

在業務場景上,咱們假設爲,咱們須要操做「書」這個實體,包括建立create,獲取詳情read,修改update,刪除delete,批量獲取multi,共五個操做方法。實體屬性有:惟一標識id,標題title,做者author。

咱們把這些操做定義成 $resource 的資源:

  var app = angular.module('Demo', ['ngResource'], angular.noop);
  app.controller('BookCtrl', function($scope, $resource){    var actions = {
      create: {method: 'POST', params: {_method: 'create'}},
      read: {method: 'POST', params: {_method: 'read'}},
      update: {method: 'POST', params: {_method: 'update'}},      delete: {method: 'POST', params: {_method: 'delete'}},
      multi: {method: 'POST', params: {_method: 'multi'}}
    }    var Book = $resource('/book', {}, actions);
  });

定義是使用使用 $resource 這個函數就能夠了,它接受三個參數:

  • url

  • 默認的params(這裏的 params 便是 GET 請求的參數,POST 的參數單獨叫作「postData」)

  • 方法映射

方法映射是以方法名爲 key ,以一個對象爲 value ,這個 value 能夠有三個成員:

  • method, 請求方法,'GET', 'POST', 'PUT', 'DELETE' 這些

  • params, 默認的 GET 參數

  • isArray, 返回的數據是否是一個列表

15.3. 基本使用

在定義了資源以後,咱們看若是使用這些資源,發出請求:

  var book = Book.read({id: '123'}, function(response){
    console.log(response);
  });

這裏咱們進行 Book 的「類」方法調用。在方法的使用上,根據官方文檔:

  HTTP GET "class" actions: Resource.action([parameters], [success], [error])
  non-GET "class" actions: Resource.action([parameters], postData, [success], [error])
  non-GET instance actions: instance.$action([parameters], [success], [error])

咱們這裏是第二種形式,即類方法的非 GET 請求。咱們給的參數會做爲 postData 傳遞。若是咱們須要 GET 參數,而且還須要一個錯誤回調,那麼:

  var book = Book.read({get: 'haha'}, {id: '123'},    function(response){
      console.log(response);
    },    function(error){
      console.log(error);
    }
  );

調用以後,咱們會當即獲得的 book ,它是 Book 類的一個實例。這裏所謂的實例,實際上就是先把全部的 action 加一個 $ 前綴放到一個空對象裏,而後把發出的參數填充進去。等請求返回了,把除 action 之外的成員刪除掉,再把請求返回的數據填充到這個對象當中。因此,若是咱們這樣:

  var book = Book.read({id: '123'}, function(response){
    console.log(book);
  });
  console.log(book)

就能看到 book 實例的變化過程了。

如今咱們獲得一個真實的實例,看一下實例的調用過程:

  //響應的數據是 {result: 0, msg: '', obj: {id: 'xxx'}}
  var book = Book.create({title: '測試標題', author: '測試做者'}, function(response){
    console.log(book);
  });

能夠看到,在請求回調以後, book 這個實例的成員已經被響應內容填充了。可是這裏有一個問題,咱們返回的數據,並不適合一個 book 實例。格式先不說,它把 title 和 author 這些信息都丟了(由於響應只返回了 id )。

若是僅僅是格式問題,咱們能夠經過配置 $http 服務來解決( AJAX 請求都要使用 $http 服務的):

  $http.defaults.transformResponse = function(data){return angular.fromJson(data).obj};

固然,咱們也能夠本身來解決一下丟信息的問題:

  var p = {title: '測試標題', author: '測試做者'};  var book = Book.create(p, function(response){
    angular.extend(book, p);
    console.log(book);
  });

不過,始終會有一些不方便了。比較正統的方式應該是調節服務器端的響應,讓服務器端也具備和前端同樣的實例概念,返回的是完整的實例信息。即便這樣,你也還要考慮格式的事。

如今咱們獲得了一個真實的 book 實例了,帶有 id 信息。咱們嘗試一下實例的方法調用,先回過去頭看一下那三種調用形式,對於實例只有第三種形式:

  non-GET instance actions: instance.$action([parameters], [success], [error])

首先解決一個疑問,若是一個實例是進行一個 GET 的調用會怎麼樣?沒有任何問題,這固然沒有任何問題的,形式和上面同樣。

如何實例是作 POST 請求的話,從形式上看,咱們沒法控制請求的 postData ?是的,全部的 POST 請求,其 postData 都會被實例數據自動填充,形式上咱們只能控制 params 。

因此,若是是在作修改調用的話:

  book.$update({title: '新標題', author: '測試做者'}, function(response){
    console.log(book);
  });

這樣是沒有意義的而且錯誤的。由於要修改的數據只是做爲 GET 參數傳遞了,而 postData 傳遞的數據就是當前實例的數據,並無任何修改。

正確的作法:

  book.title = '新標題'
  book.$update(function(response){
    console.log(book);
  });

顯然,這種狀況下,回調均可以省了:

  book.title = '新標題'
  book.$update();

15.4. 定義和使用時的佔位量

兩方面。一是在定義時,在其 URL 中可使用變量引用的形式(類型於定義錨點路由時那樣)。第二時定義默認 params ,即 GET 參數時,能夠定義爲引用 postData 中的某變量。好比咱們這樣改一下:

  var Book = $resource('/book/:id', {}, actions);  var book = Book.read({id: '123'}, {}, function(response){
    console.log(response);
  });

在 URL 中有一個 :id ,表示對 params 中 id 這個變量的引用。由於 read 是一個 POST 請求,根據調用形式,第一個參數是 params ,第二個參數是 postData 。這樣的調用結果就是,咱們會發一個 POST 請求到以下地址, postData 爲空:

  /book/123?_method=read

再看默認的 params 中引用 postData 變量的形式:

  var Book = $resource('/book', {id: '@id'}, actions);  var book = Book.read({title: 'xx'}, {id: '123'}, function(response){
    console.log(response);
  });

這樣會出一個 POST 請求, postData 內容中有一個 id 數據,訪問的 URL 是:

  /book?_method=read&id=123&title=xx

這兩個機制也能夠聯合使用:

  var Book = $resource('/book/:id', {id: '@id'}, actions);  var book = Book.read({title: 'xx'}, {id: '123'}, function(response){
    console.log(response);
  });

結果就是出一個 POST 請求, postData 內容中有一個 id 數據,訪問的 URL 是:

  /book/123?_method=read&title=xx

15.5. 實例

ngResource 要舉一個實例是比較麻煩的事。由於它必需要一個後端來支持,這裏若是我用 Python 寫一個簡單的後端,估計要讓這個後端跑起來對不少人來講都是問題。因此,我在幾套公共服務的 API 中糾結考察了一番,最後使用 www.rememberthemilk.com 的 API 來作了一個簡單的,可用的例子。

例子見: http://zouyesheng.com/demo/ng-resource-demo.html (能夠直接下載看源碼)

先說一下 API 的狀況。這裏的請求調用全是跨域的,因此交互上所有是使用了 JSONP 的形式。 API 的使用有使用簽名認證機制,嗯, js 中直接算 md5 是可行的,我用了一個現成的庫(可是好像不能處理中文吧)。

這個例子中的 LoginCtrl 你們就不用太關心了,參見官方的文檔,走完流程拿到 token 完事。與ngResource 相關的是 MainCtrl 中的東西。

其實從這個例子中就能夠看出,目前 ngResource 的機制對於服務端返回的數據的格式是嚴重依賴的,同時也能夠反映出 $http 對一些場景根本沒法應對的侷限。因此,我如今的想法是理解ngResource 的思想,真正須要的人本身使用 jQuery 從新實現一遍也許更好。這應該也花不了多少時間, ngResource 的代碼原本很少。

我爲何說 $http 在一些場景中有侷限呢。在這個例子當中,全部的請求都須要帶一個簽名,簽名值是由請求中帶的參數根據規則使用 md5 方法計算出的值。我找不到一個 hook 可讓我在請求出去以前修改這個請求(添加上簽名)。因此在這個例子當中,個人作法是根據 ngResource 的請求最後會使用 $httpBackend 這個底層服務,在 module 定義時我本身複製官方的相關代碼,從新定義 $httpBackend 服務,在須要的地方作我本身的修改:

  script.src = sign_url(url);

不錯,我就改了這一句,但我不得不復制了 50 行官方源碼到個人例子中。

另一個須要說的是對返回數據的處理。由於 ngResource 會使用返回的數據直接填充實例,因此這個數據格式就很重要。

首先,咱們可使用 $http.defaults.transformResponse 來統一處理一下返回的數據,可是這並不能解決全部問題,可目前 ngResource 並不提供對每個 action 的單獨的後處理回調函數項。除非你的服務端是通過專門的適應性設計的,不然你用 ngResource 不可能爽。例子中,我爲了獲取當前列表的結果,我不得不本身去封裝結果:

  var list_list = List.getList(function(){    var res = list_list[1];    while(list_list.length > 0){list_list.pop()};
    angular.forEach(res.list, function(v){
      list_list.push(new List({list: v}));
    });
    $scope.list_list = list_list;
    $scope.show_add = true;    return;
  });

16. AngularJS與其它框架的混用(jQuery, Dojo)

這個問題彷佛不少人都關心,可是事實是,若是瞭解了 ng 的工做方式,這原本就不是一個問題了。

在我本身使用 ng 的過程中,一直是混用 jQuery 的,之前還要加上一個 Dojo 。只要瞭解每種框架的工做方式,在具體的代碼中每一個框架都作了什麼事,那麼總體上控制起來就不會有問題。

回到 ng 上來看,首先對於 jQuery 來講,最開始說提到過,在 DOM 操做部分, ng 與 jQuery 是兼容的,若是沒有 jQuery , ng 本身也實現了兼容的部分 API 。

同時,最開始也提到過, ng 的使用最忌諱的一點就是修改 DOM 結構——你應該使用 ng 的模板機制進行數據綁定,以此來控制 DOM 結構,而不是直接操做。換句話來講,在不動 DOM 結構的這個前提之下,你的數據隨便怎麼改,隨便使用哪一個框架來控制都是沒問題的,到時若有必要使用$scope.$digest() 來通知 ng 一下便可。

下面這個例子,咱們使用了 jQuery 中的 Deferred ( $.ajax 就是返回一個 Deferred ),還使用了 ng 的 $timeout ,固然是在 ng 的結構之下:

 1   <!DOCTYPE html> 2   <html ng-app="Demo"> 3   <head> 4   <meta charset="utf-8" /> 5   <title>AngularJS</title> 6   </head> 7   <body> 8    9   <div ng-controller="TestCtrl">10     <span ng-click="go()">{{ a }}</span>11   </div>12   13   <script type="text/javascript"14     src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">15   </script>16   <script type="text/javascript"17     src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js">18   </script>19   20   <script type="text/javascript">21   var app = angular.module('Demo', [], angular.noop);22   app.controller('TestCtrl', function($scope, $timeout){23     $scope.a = '點擊我開始';24   25     var defer = $.Deferred();26     var f = function(){27       if($scope.a == ''){$scope.a = '已中止'; return}28       defer.done(function(){29         $scope.a.length < 10 ? $scope.a += '>' : $scope.a = '>';30         $timeout(f, 100);31       });32     }33     defer.done(function(){$scope.a = '>'; f()});34   35     $scope.go = function(){36       defer.resolve();37       $timeout(function(){$scope.a = ''}, 5000);38     }39   });40   </script>41   </body>42   </html>

再把 Dojo 加進來看與 DOM 結構相關的例子。以前說過,使用 ng 就最好不要手動修改 DOM 結構,但這裏說兩點:

  1. 對於整個頁面,你能夠只在局部使用 ng ,不使用 ng 的地方你能夠隨意控制 DOM 。

  2. 若是 DOM 結構有變更,你能夠在 DOM 結構定下來以後再初始化 ng 。

下面這個例子使用了 AngularJS , jQuery , Dojo :

 1   <!DOCTYPE html> 2   <html> 3   <head> 4   <meta charset="utf-8" /> 5   <title>AngularJS</title> 6   <link rel="stylesheet" 7     href="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dijit/themes/claro/claro.css" media="screen" /> 8   </head> 9   <body class="claro">10   11   <div ng-controller="TestCtrl" id="test_ctrl">12   13     <p ng-show="!btn_disable">14       <button ng-click="change()">調用dojo修改按鈕</button>15     </p>16   17     <p id="btn_wrapper">18       <button data-dojo-type="dijit/form/Button" type="button">{{ a }}</button>19     </p>20   21     <p>22       <input ng-model="dialog_text" ng-init="dialog_text='對話框內容'" />23       <button ng-click="dialog(dialog_text)">顯示對話框</button>24     </p>25   26     <p ng-show="show_edit_text" style="display: none;">27       <span>須要編輯的內容:</span>28       <input ng-model="text" />29     </p>30   31     <div id="editor_wrapper">32       <div data-dojo-type="dijit/Editor" id="editor"></div>33     </div>34   35   </div>36   37   38   <script type="text/javascript"39     src="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js">40   </script>41   <script type="text/javascript"42     src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">43   </script>44   <script type="text/javascript"45     src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js">46   </script>47   48   <script type="text/javascript">49   50   require(['dojo/parser', 'dijit/Editor'], function(parser){51     parser.parse($('#editor_wrapper')[0]).then(function(){52       var app = angular.module('Demo', [], angular.noop);53   54       app.controller('TestCtrl', function($scope, $timeout){55         $scope.a = '我是ng, 也是dojo';56         $scope.show_edit_text = true;57   58         $scope.change = function(){59           $scope.a = 'DOM結構已經改變(不建議這樣作)';60           require(['dojo/parser', 'dijit/form/Button', 'dojo/domReady!'],61             function(parser){62               parser.parse($('#btn_wrapper')[0]);63               $scope.btn_disable = true;64             }65           );66         }67   68         $scope.dialog = function(text){69           require(["dijit/Dialog", "dojo/domReady!"], function(Dialog){70             var dialog = new Dialog({71                 title: "對話框哦",72                 content: text,73                 style: "width: 300px"74             });75             dialog.show();76           });77         }78   79         require(['dijit/registry'], function(registry){80           var editor = registry.byId('editor');81           $scope.$watch('text', function(new_v){82             editor.setValue(new_v);83           });84         });85   86       });87   88       angular.bootstrap(document, ['Demo']);89     });90   91   });92   93   </script>94   </body>95   </html>

17. 自定義過濾器

先來回顧一下 ng 中的一些概念:

  • module ,代碼的組織單元,其它東西都是在定義在具體的模塊中的。

  • app ,業務概念,可能會用到多個模塊。

  • service ,僅在數據層面實現特定業務功能的代碼封裝。

  • controller ,與 DOM 結構相關聯的東西,便是一種業務封裝概念,又體現了項目組織的層級結構。

  • filter ,改變輸入數據的一種機制。

  • directive ,與 DOM 結構相關聯的,特定功能的封裝形式。

上面的這幾個概念基本上就是 ng 的所有。每一部分均可以自由定義,使用時經過各要素的相互配合來實現咱們的業務需求。

咱們從最開始一致打交道的東西基本上都是 controller 層面的東西。在前面,也介紹了 module 和service 的自定義。剩下的會介紹 filter 和 directive 的定義。基本上這幾部分的定義形式都是同樣的,原理上是經過 provider 來作注入形式的聲明,在實際操做過程當中,又有不少 shortcut 式的聲明方式。

過濾器的自定義是最簡單的,就是一個函數,接受輸入,而後返回結果。在考慮過濾器時,我以爲很重要的一點: 無狀態 。

具體來講,過濾器就是一個函數,函數的本質含義就是肯定的輸入必定獲得肯定的輸出。雖然 filter是定義在 module 當中的,並且 filter 又是在 controller 的 DOM 範圍內使用的,可是,它和具體的module , controller , scope 這些概念都沒有關係(雖然在這裏你可使用 js 的閉包機制玩些花樣),它僅僅是一個函數,而已。換句話說,它沒有任何上下文關聯的能力。

過濾器基本的定義方式:

  var app = angular.module('Demo', [], angular.noop);
  app.filter('map', function(){    var filter = function(input){      return input + '...';
    };    return filter;
  });

上面的代碼定義了一個叫作 map 的過濾器。使用時:

  <p>示例數據: {{ a|map }}</p>

過濾器也能夠帶參數,多個參數之間使用 : 分割,看一個完整的例子:

 1   <div ng-controller="TestCtrl"> 2   <p>示例數據: {{ a|map:map_value:'>>':'(no)' }}</p> 3   <p>示例數據: {{ b|map:map_value:'>>':'(no)' }}</p> 4   </div> 5    6    7   <script type="text/javascript"> 8    9   var app = angular.module('Demo', [], angular.noop);10   app.controller('TestCtrl', function($scope){11     $scope.map_value = {12       a: '一',13       b: '二',14       c: '三'15     }16     $scope.a = 'a';17   });18   19   app.filter('map', function(){20     var filter = function(input, map_value, append, default_value){21       var r = map_value[input];22       if(r === undefined){ return default_value + append }23       else { return r + append }24     };25     return filter;26   });27   28   angular.bootstrap(document, ['Demo']);29   </script>

18. 自定義指令directive

這是 ng 最強大的一部分,也是最複雜最讓人頭疼的部分。

目前咱們看到的所謂「模板」系統,只不過是官方實現的幾個指令而已。這意味着,經過自定義各類指令,咱們不但能夠徹底定義一套「模板」系統,更能夠把 HTML 頁面直接打形成爲一種 DSL (領域特定語言)。

18.1. 指令的使用

使用指令時,它的名字能夠有多種形式,把指令放在什麼地方也有多種選擇。

一般,指令的定義名是形如 ngBind 這樣的 「camel cased」 形式。在使用時,它的引用名能夠是:

  • ng:bind

  • ng_bind

  • ng-bind

  • x-ng-bind

  • data-ng-bind

你能夠根據你本身是否有 「HTML validator」 潔癖來選擇。

指令能夠放在多個地方,它們的做用相同:

  • <span my-dir="exp"></span> 做爲標籤的屬性

  • <span class="my-dir: exp;"></span> 做爲標籤類屬性的值

  • <my-dir></my-dir> 做爲標籤

  • <!-- directive: my-dir exp --> 做爲註釋

這些方式可使用指令定義中的 restrict 屬性來控制。

能夠看出,指令便可以做爲標籤使用,也能夠做爲屬性使用。仔細考慮一下,這在類 XML 的結構當中真算得上是一種神奇的機制。

18.2. 指令的執行過程

ng 中對指令的解析與執行過程是這樣的:

  • 瀏覽器獲得 HTML 字符串內容,解析獲得 DOM 結構。

  • ng 引入,把 DOM 結構扔給 $compile 函數處理:

    • 找出 DOM 結構中有變量佔位符

    • 匹配找出 DOM 中包含的全部指令引用

    • 把指令關聯到 DOM

    • 關聯到 DOM 的多個指令按權重排列

    • 執行指令中的 compile 函數(改變 DOM 結構,返回 link 函數)

    • 獲得的全部 link 函數組成一個列表做爲 $compile 函數的返回

  • 執行 link 函數(鏈接模板的 scope)。

18.3. 基本的自定義方法

自定義一個指令能夠很是很是的複雜,可是其基本的調用形式,同自定義服務大概是相同的:

  <p show style="font-size: 12px;"></p>  
  <script type="text/javascript">
  
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('show', function(){    var func = function($scope, $element, $attrs){
      console.log($scope);
      console.log($element);
      console.log($attrs);
    }    
    return func;    //return {compile: function(){return func}}
  });
  
  angular.bootstrap(document, ['Demo']);  </script>

若是在 directive 中直接返回一個函數,則這個函數會做爲 compile 的返回值,也便是做爲link 函數使用。這裏說的 compile 和 link 都是一個指令的組成部分,一個完整的定義應該返回一個對象,這個對象包括了多個屬性:

  • name

  • priority

  • terminal

  • scope

  • controller

  • require

  • restrict

  • template

  • templateUrl

  • replace

  • transclude

  • compile

  • link

上面的每個屬性,均可以單獨探討的。

下面是一個完整的基本的指令定義例子:

  <code lines>
  //失去焦點使用 jQuery 的擴展支持冒泡
  app.directive('ngBlur', function($parse){
    return function($scope, $element, $attr){
      var fn = $parse($attr['ngBlur']);
      $element.on('focusout', function(event){
        fn($scope, {$event: event});
      });
    }
  });
  </code>
  
  <div code lines>
  //失去焦點使用 jQuery 的擴展支持冒泡
  app.directive('ngBlur', function($parse){
    return function($scope, $element, $attr){
      var fn = $parse($attr['ngBlur']);
      $element.on('focusout', function(event){
        fn($scope, {$event: event});
      });
    }
  });
  </div>
 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('code', function(){ 4     var func = function($scope, $element, $attrs){ 5    6       var html = $element.text(); 7       var lines = html.split('\n'); 8    9       //處理首尾空白10       if(lines[0] == ''){lines = lines.slice(1, lines.length - 1)}11       if(lines[lines.length-1] == ''){lines = lines.slice(0, lines.length - 1)}12   13       $element.empty();14   15       //處理外框16       (function(){17         $element.css('clear', 'both');18         $element.css('display', 'block');19         $element.css('line-height', '20px');20         $element.css('height', '200px');21       })();22   23       //是否顯示行號的選項24       if('lines' in $attrs){25         //處理行號26         (function(){27           var div = $('<div style="width: %spx; background-color: gray; float: left; text-align: right; padding-right: 5px; margin-right: 10px;"></div>'28                       .replace('%s', String(lines.length).length * 10));29           var s = '';30           angular.forEach(lines, function(_, i){31             s += '<pre style="margin: 0;">%s</pre>\n'.replace('%s', i + 1);32           });33           div.html(s);34           $element.append(div);35         })();36       }37   38       //處理內容39       (function(){40         var div = $('<div style="float: left;"></div>');41         var s = '';42         angular.forEach(lines, function(l){43           s += '<span style="margin: 0;">%s</span><br />\n'.replace('%s', l.replace(/\s/g, '<span>&nbsp;</span>'));44         });45         div.html(s);46         $element.append(div);47       })();48     }49   50     return {link: func,51             restrict: 'AE'}; //以元素或屬性的形式使用命令52   });53   54   angular.bootstrap(document, ['Demo']);

上面這個自定義的指令,作的事情就是解析節點中的文本內容,而後修改它,再把生成的新內容填充到節點當中去。其間還涉及了節點屬性值 lines 的處理。這算是指令中最簡單的一種形式。由於它是「一次性使用」,中間沒有變量的處理。好比若是節點原來的文本內容是一個變量引用,相似於{{ code }} ,那上面的代碼就不行了。這種狀況麻煩得多。後面會討論。

18.4. 屬性值類型的自定義

官方代碼中的 ng-show 等算是我說的這種類型。使用時主要是在節點加添加一個屬性值以附加額外的功能。看一個簡單的例子:

  <p color="red">有顏色的文本</p>
  <color color="red">有顏色的文本</color>
  
  <script type="text/javascript">  
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('color', function(){    var link = function($scope, $element, $attrs){
      $element.css('color', $attrs.color);
    }    return {link: link,
            restrict: 'AE'};
  });
  
  angular.bootstrap(document, ['Demo']);
  </script>

咱們定義了一個叫 color 的指令,能夠指定節點文本的顏色。可是這個例子還沒法像 ng-show 那樣工做的,這個例子只能渲染一次,而後就沒法根據變量來從新改變顯示了。要響應變化,咱們須要手工使用 scope 的 $watch 來處理:

 1    2   <div ng-controller="TestCtrl"> 3     <p color="color">有顏色的文本</p> 4     <p color="'blue'">有顏色的文本</p> 5   </div> 6    7   <script type="text/javascript"> 8    9   var app = angular.module('Demo', [], angular.noop);10   11   app.directive('color', function(){12     var link = function($scope, $element, $attrs){13       $scope.$watch($attrs.color, function(new_v){14         $element.css('color', new_v);15       });16     }17     return link;18   });19   20   app.controller('TestCtrl', function($scope){21     $scope.color = 'red';22   });23   24   angular.bootstrap(document, ['Demo']);25   </script>

18.5. Compile的細節

指令的處理過程,是 ng 的 Compile 過程的一部分,它們也是緊密聯繫的。繼續深刻指令的定義方法,首先就要對 Compile 的過程作更細緻的瞭解。

前面說過, ng 對頁面的處理過程:

  • 瀏覽器把 HTML 字符串解析成 DOM 結構。

  • ng 把 DOM 結構給 $compile ,返回一個 link 函數。

  • 傳入具體的 scope 調用這個 link 函數。

  • 獲得處理後的 DOM ,這個 DOM 處理了指令,鏈接了數據。

$compile 最基本的使用方式:

  var link = $compile('<p>{{ text }}</p>');  var node = link($scope);
  console.log(node);

上面的 $compile 和 link 調用時都有額外參數來實現其它功能。先看 link 函數,它形如:

  function(scope[, cloneAttachFn]

第二個參數 cloneAttachFn 的做用是,代表是否複製原始節點,及對複製節點須要作的處理,下面這個例子說明了它的做用:

  <div ng-controller="TestCtrl"></div>
  <div id="a">A {{ text }}</div>
  <div id="b">B </div>
  app.controller('TestCtrl', function($scope, $compile){    var link = $compile($('#a'));  
    //true參數表示新建一個徹底隔離的scope,而不是繼承的child scope
    var scope = $scope.$new(true);
    scope.text = '12345';  
    //var node = link(scope, function(){});
    var node = link(scope);
  
    $('#b').append(node);
  });

cloneAttachFn 對節點的處理是有限制的,你能夠添加 class ,可是不能作與數據綁定有關的其它修改(修改了也無效):

  app.controller('TestCtrl', function($scope, $compile){    var link = $compile($('#a'));    var scope = $scope.$new(true);
    scope.text = '12345';  
    var node = link(scope, function(clone_element, scope){
      clone_element.text(clone_element.text() + ' ...'); //無效
      clone_element.text('{{ text2 }}'); //無效
      clone_element.addClass('new_class');
    });
  
    $('#b').append(node);
  });

修改無效的緣由是,像 {{ text }} 這種所謂的 Interpolate 在 $compile 中已經被處理過了,生成了相關函數(這裏起做用的是 directive 中的一個 postLink 函數),後面執行 link 就是執行了 $compile 生成的這些函數。固然,若是你的文本沒有數據變量的引用,那修改是會有效果的。

前面在說自定義指令時說過, link 函數是由 compile 函數返回的,也就像前面說的,應該把改變 DOM 結構的邏輯放在 compile 函數中作。

$compile 還有兩個額外的參數:

  $compile(element, transclude, maxPriority);

maxPriority 是指令的權重限制,這個容易理解,後面再說。

transclude 是一個函數,這個函數會傳遞給 compile 期間找到的 directive 的 compile 函數(編譯節點的過程當中找到了指令,指令的 compile 函數會接受編譯時傳遞的 transclude 函數做爲其參數)。

可是在實際使用中,除咱們手工在調用 $compile 以外,初始化時的根節點 compile 是不會傳遞這個參數的。

在咱們定義指令時,它的 compile 函數是這個樣子的:

  function compile(tElement, tAttrs, transclude) { ... }

事實上, transclude 的值,就是 directive 所在的 原始 節點,把原始節點從新作了編譯以後獲得的 link 函數(須要 directive 定義時使用 transclude 選項),後面會專門演示這個過程。因此,官方文檔上也把 transclude 函數描述成 link 函數的樣子(若是自定義的指令只用在本身手動 $compile 的環境中,那這個函數的形式是能夠隨意的):

  {function(angular.Scope[, cloneAttachFn]}

因此記住,定義指令時, compile 函數的第三個參數 transclude ,就是一個 link ,裝入scope 執行它你就獲得了一個節點。

18.6. transclude的細節

transclude 有兩方面的東西,一個是使用 $compile 時傳入的函數,另外一個是定義指令的compile 函數時接受的一個參數。雖然這裏的一出一進原本是相互對應的,可是實際使用中,由於大部分時候不會手動調用 $compile ,因此,在「默認」狀況下,指令接受的 transclude 又會是一個比較特殊的函數。

看一個基本的例子:

  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('more', function(){    var func = function(element, attrs, transclude){      var sum = transclude(1, 2);
      console.log(sum);
      console.log(element);  
    }  
    return {compile: func,
            restrict: 'E'};
  });
  
  app.controller('TestCtrl', function($scope, $compile, $element){    var s = '<more>123</more>';    var link = $compile(s, function(a, b){return a + b});    var node = link($scope);
    $element.append(node);
  });
  
  angular.bootstrap(document, ['Demo']);

咱們定義了一個 more 指令,它的 compile 函數的第三個參數,就是咱們手工 $compile 時傳入的。

若是不是手工 $compile ,而是 ng 初始化時找出的指令,則 transclude 是一個 link 函數(指令定義須要設置 transclude 選項):

  <div more>123</div>
  app.directive('more', function($rootScope, $document){    var func = function(element, attrs, link){      var node = link($rootScope);
      node.removeAttr('more'); //不去掉就變死循環了
      $('body', $document).append(node);
    }  
    return {compile: func,
            transclude: 'element', // element是節點沒,其它值是節點的內容沒
            restrict: 'A'};
  });

18.7. 把節點內容做爲變量處理的類型

回顧最開始的那個代碼顯示的例子,那個例子只能處理一次節點內容。若是節點的內容是一個變量的話,須要用另外的思路來考慮。這裏咱們假設的例子是,定義一個指令 showLenght ,它的做用是在一段文本的開頭顯示出這段節點文本的長度,節點文本是一個變量。指令使用的形式是:

  <div ng-controller="TestCtrl">
    <div show-length>{{ text }}</div>
    <button ng-click="text='xx'">改變</button>
  </div>

從上面的 HTML 代碼中,大概清楚 ng 解析它的過程(只看 show-length 那一行):

  • 解析 div 時發現了一個 show-length 的指令。

  • 若是 show-length 指令設置了 transclude 屬性,則 div 的節點內容被從新編譯,獲得的 link 函數做爲指令 compile 函數的參數傳入。

  • 若是 show-length 指令沒有設置 transclude 屬性,則繼續處理它的子節點(TextNode )。

  • 無論是上面的哪一種狀況,都會繼續處理到 {{ text }} 這段文本。

  • 發現 {{ text }} 是一個 Interpolate ,因而自動在此節點中添加了一個指令,這個指令的 link 函數就是爲 scope 添加了一個 $watch ,實現的功能是是當 scope 做$digest 的時候,就更新節點文本。

與處理 {{ text }} 時添加的指令相同,咱們實現 showLength 的思路,也就是:

  • 修改原來的 DOM 結構

  • 爲 scope 添加 $watch ,當 $digest 時修改指定節點的文本,其值爲指定節點文本的長度。

代碼以下:

  app.directive('showLength', function($rootScope, $document){    var func = function(element, attrs, link){  
      return function(scope, ielement, iattrs, controller){        var node = link(scope);
        ielement.append(node);        var lnode = $('<span></span>');
        ielement.prepend(lnode);
  
        scope.$watch(function(scope){
          lnode.text(node.text().length);
        });
      };
    }  
    return {compile: func,
            transclude: true, // element是節點沒,其它值是節點的內容沒
            restrict: 'A'};
  });

上面代碼中,由於設置了 transclude 屬性,咱們在 showLength 的 link 函數(就是 return的那個函數)中,使用 func 的第三個函數來重塑了原來的文本節點,並放在咱們須要的位置上。而後,咱們添加本身的節點來顯示長度值。最後給當前的 scope 添加 $watch ,以更新這個長度值。

18.8. 指令定義時的參數

指令定義時的參數以下:

  • name

  • priority

  • terminal

  • scope

  • controller

  • require

  • restrict

  • template

  • templateUrl

  • replace

  • transclude

  • compile

  • link

如今咱們開始一個一個地吃掉它們……,可是並非按順序講的。

  • priority

  • 這個值設置指令的權重,默認是 0 。當一個節點中有多個指令存在時,就按着權限從大到小的順序依次執行它們的 compile 函數。相同權重順序不定。

  • terminal

  • 是否以當前指令的權重爲結束界限。若是這值設置爲 true ,則節點中權重小於當前指令的其它指令不會被執行。相同權重的會執行。

  • restrict

  • 指令能夠以哪些方式被使用,能夠同時定義多種方式。

    • E 元素方式 <my-directive></my-directive>

    • A 屬性方式 <div my-directive="exp"> </div>

    • C 類方式 <div class="my-directive: exp;"></div>

    • M 註釋方式 <!-- directive: my-directive exp -->

  • transclude

  • 前面已經講過基本的用法了。能夠是 'element' 或 true 兩種值。

  • compile

  • 基本的定義函數。 function compile(tElement, tAttrs, transclude) { ... }

  • link

  • 前面介紹過了。大多數時候咱們不須要單獨定義它。只有 compile 未定義時 link 纔會被嘗試。function link(scope, iElement, iAttrs, controller) { ... }

  • scope

  • scope 的形式。 false 節點的 scope , true 繼承建立一個新的 scope , {} 不繼承建立一個新的隔離 scope 。 {@attr: '引用節點屬性', =attr: '把節點屬性值引用成scope屬性值', &attr: '把節點屬性值包裝成函數'}

  • controller

  • 爲指令定義一個 controller , function controller($scope, $element, $attrs, $transclude) { ... }

  • name

  • 指令的 controller 的名字,方便其它指令引用。

  • require

  • 要引用的其它指令 conroller 的名字, ?name 忽略不存在的錯誤, ^name 在父級查找。

  • template

  • 模板內容。

  • templateUrl

  • 從指定地址獲取模板內容。

  • replace

  • 是否使用模板內容替換掉整個節點, true 替換整個節點, false 替換節點內容。

  <a b></a>
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('a', function(){    var func = function(element, attrs, link){
      console.log('a');
    }  
    return {compile: func,
            priority: 1,
            restrict: 'EA'};
  });
  
  app.directive('b', function(){    var func = function(element, attrs, link){
      console.log('b');
    }  
    return {compile: func,
            priority: 2,            //terminal: true,
            restrict: 'A'};
  });

上面幾個參數值都是比較簡單且容易理想的。

再看 scope 這個參數:

  <div ng-controller="TestCtrl">
    <div a b></div>
  </div>
 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(element, attrs, link){ 5       return function(scope){ 6         console.log(scope); 7       } 8     } 9   10     return {compile: func,11             scope: true,12             restrict: 'A'};13   });14   15   app.directive('b', function(){16     var func = function(element, attrs, link){17       return function(scope){18         console.log(scope);19       }20     }21   22     return {compile: func,23             restrict: 'A'};24   });25   26   app.controller('TestCtrl', function($scope){27     $scope.a = '123';28     console.log($scope);29   });

對於 scope :

  • 默認爲 false , link 函數接受的 scope 爲節點所在的 scope 。

  • 爲 true 時,則 link 函數中第一個參數(還有 controller 參數中的 $scope ),scope 是節點所在的 scope 的 child scope ,而且若是節點中有多個指令,則只要其中一個指令是 true 的設置,其它全部指令都會受影響。

這個參數還有其它取值。當其爲 {} 時,則 link 接受一個徹底隔離(isolate)的 scope ,於true 的區別就是不會繼承其它 scope 的屬性。可是這時,這個 scope 的屬性卻能夠有很靈活的定義方式:

@attr 引用節點的屬性。

  <div ng-controller="TestCtrl">
    <div a abc="here" xx="{{ a }}" c="ccc"></div>
  </div>
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('a', function(){    var func = function(element, attrs, link){      return function(scope){
        console.log(scope);
      }
    }  
    return {compile: func,
            scope: {a: '@abc', b: '@xx', c: '@'},
            restrict: 'A'};
  });
  
  app.controller('TestCtrl', function($scope){
    $scope.a = '123';
  });
  • @abc 引用 div 節點的 abc 屬性。

  • @xx 引用 div 節點的 xx 屬性,而 xx 屬性又是一個變量綁定,因而 scope 中 b 屬性值就和 TestCtrl 的 a 變量綁定在一塊兒了。

  • @ 沒有寫 attr name ,則默認取本身的值,這裏是取 div 的 c 屬性。

=attr 類似,只是它把節點的屬性值當成節點 scope 的屬性名來使用,做用至關於上面例子中的@xx :

  <div ng-controller="TestCtrl">
    <div a abc="here"></div>
  </div>
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('a', function(){    var func = function(element, attrs, link){      return function(scope){
        console.log(scope);
      }
    }  
    return {compile: func,
            scope: {a: '=abc'},
            restrict: 'A'};
  });
  
  app.controller('TestCtrl', function($scope){
    $scope.here = '123';
  });

&attr 是包裝一個函數出來,這個函數以節點所在的 scope 爲上下文。來看一個很爽的例子:

  <div ng-controller="TestCtrl">
    <div a abc="here = here + 1" ng-click="show(here)">這裏</div>
    <div>{{ here }}</div>
  </div>
 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(element, attrs, link){ 5       return function llink(scope){ 6         console.log(scope); 7         scope.a(); 8         scope.b(); 9   10         scope.show = function(here){11           console.log('Inner, ' + here);12           scope.a({here: 5});13         }14       }15     }16   17     return {compile: func,18             scope: {a: '&abc', b: '&ngClick'},19             restrict: 'A'};20   });21   22   app.controller('TestCtrl', function($scope){23     $scope.here = 123;24     console.log($scope);25   26     $scope.show = function(here){27       console.log(here);28     }29   });

scope.a 是 &abc ,即:

  scope.a = function(){here = here + 1}

只是其中的 here 是 TestCtrl 的。

scope.b 是 &ngClick ,即:

  scope.b = function(){show(here)}

這裏的 show() 和 here 都是 TestCtrl 的,因而上面的代碼最開始會在終端輸出一個 124 。

當點擊「這裏」時,這時執行的 show(here) 就是 llink 中定義的那個函數了,與 TestCtrl 無關。可是,其間的 scope.a({here:5}) ,由於 a 執行時是 TestCtrl 的上下文,因而向 a 傳遞的一個對象,裏面的全部屬性 TestCtrl 就全收下了,接着執行 here=here+1 ,因而咱們會在屏幕上看到 6 。

這裏是一個上下文交錯的環境,經過 & 這種機制,讓指令的 scope 與節點的 scope 發生了互動。真是鬼斧神工的設計。而實現它,只用了幾行代碼:

  case '&': {
    parentGet = $parse(attrs[attrName]);
    scope[scopeName] = function(locals) {      return parentGet(parentScope, locals);
    }    break;
  }

再看 controller 這個參數。這個參數的做用是提供一個 controller 的構造函數,它會在 compile 函數以後, link 函數以前被執行。

  <a>haha</a>
 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(){ 5       console.log('compile'); 6       return function(){ 7         console.log('link'); 8       } 9     }10   11     var controller = function($scope, $element, $attrs, $transclude){12       console.log('controller');13       console.log($scope);14   15       var node = $transclude(function(clone_element, scope){16         console.log(clone_element);17         console.log('--');18         console.log(scope);19       });20       console.log(node);21     }22   23     return {compile: func,24             controller: controller,25             transclude: true,26             restrict: 'E'}27   });

controller 的最後一個參數, $transclude ,是一個只接受 cloneAttachFn 做爲參數的一個函數。

按官方的說法,這個機制的設計目的是爲了讓各個指令之間能夠互相通訊。參考普通節點的處理方式,這裏也是處理指令 scope 的合適位置。

  <a b>kk</a>
 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(){ 5     } 6    7     var controller = function($scope, $element, $attrs, $transclude){ 8       console.log('a'); 9       this.a = 'xx';10     }11   12     return {compile: func,13             name: 'not_a',14             controller: controller,15             restrict: 'E'}16   });17   18   app.directive('b', function(){19     var func = function(){20       return function($scope, $element, $attrs, $controller){21         console.log($controller);22       }23     }24   25     var controller = function($scope, $element, $attrs, $transclude){26       console.log('b');27     }28   29     return {compile: func,30             controller: controller,31             require: 'not_a',32             restrict: 'EA'}33   });

name 參數在這裏能夠用覺得 controller 重起一個名字,以方便在 require 參數中引用。

require 參數能夠帶兩種前綴(能夠同時使用):

  • ? ,若是指定的 controller 不存在,則忽略錯誤。即:

      require: '?not_b'

    若是名爲 not_b 的 controller 不存在時,不會直接拋出錯誤, link 函數中對應的$controller 爲 undefined 。

  • ^ ,同時在父級節點中尋找指定的 controller ,把上面的例子小改一下:

      <a><b>kk</b></a>

    把 a 的 require 改爲(不然就找不到 not_a 這個 controller ):

      require: '?^not_a'

還剩下幾個模板參數:

  • template 模板內容,這個內容會根據 replace 參數的設置替換節點或只替換節點內容。


  • templateUrl 模板內容,獲取方式是異步請求。


  • replace 設置如何處理模板內容。爲 true 時爲替換掉指令節點,不然只替換到節點內容。


  <div ng-controller="TestCtrl">
    <h1 a>原始內容</h1>
  </div>
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('a', function(){    var func = function(){
    }  
    return {compile: func,
            template: '<p>標題 {{ name }} <button ng-click="name=\'hahaha\'">修改</button></p>',            //replace: true,
            //controller: function($scope){$scope.name = 'xxx'},
            //scope: {},
            scope: true ,
            controller: function($scope){console.log($scope)},
            restrict: 'A'}
  });
  
  app.controller('TestCtrl', function($scope){
    $scope.name = '123';
    console.log($scope);
  });

template 中能夠包括變量引用的表達式,其 scope 遵尋 scope 參數的做用(可能受繼承關係影響)。

templateUrl 是異步請求模板內容,而且是獲取到內容以後纔開始執行指令的 compile 函數。

最後說一個 compile 這個參數。它除了能夠返回一個函數用爲 link 函數以外,還能夠返回一個對象,這個對象能包括兩個成員,一個 pre ,一個 post 。實際上, link 函數是由兩部分組成,所謂的 preLink 和 postLink 。區別在於執行順序,特別是在指令層級嵌套的結構之下, postLink 是在全部的子級指令 link 完成以後才最後執行的。 compile 若是隻返回一個函數,則這個函數被做爲 postLink 使用:

  <a><b></b></a>
 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('a', function(){ 4     var func = function(){ 5       console.log('a compile'); 6       return { 7         pre: function(){console.log('a link pre')}, 8         post: function(){console.log('a link post')}, 9       }10     }11   12     return {compile: func,13             restrict: 'E'}14   });15   16   app.directive('b', function(){17     var func = function(){18       console.log('b compile');19       return {20         pre: function(){console.log('b link pre')},21         post: function(){console.log('b link post')},22       }23     }24   25     return {compile: func,26             restrict: 'E'}27   });

18.9. Attributes的細節

節點屬性被包裝以後會傳給 compile 和 link 函數。從這個操做中,咱們能夠獲得節點的引用,能夠操做節點屬性,也能夠爲節點屬性註冊偵聽事件。

  <test a="1" b c="xxx"></test>
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('test', function(){    var func = function($element, $attrs){
      console.log($attrs);
    }  
    return {compile: func,
            restrict: 'E'}

整個 Attributes 對象是比較簡單的,它的成員包括了:

  • $$element 屬性所在的節點。


  • $attr 全部的屬性值(類型是對象)。


  • $normalize 一個名字標準化的工具函數,能夠把 ng-click 變成 ngClick 。


  • $observe 爲屬性註冊偵聽器的函數。


  • $set 設置對象屬性,及節點屬性的工具。


除了上面這些成員,對象的成員還包括全部屬性的名字。

先看 $observe 的使用,基本上至關於 $scope 中的 $watch :

  <div ng-controller="TestCtrl">
    <test a="{{ a }}" b c="xxx"></test>
    <button ng-click="a=a+1">修改</button>
  </div>
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('test', function(){    var func = function($element, $attrs){
      console.log($attrs);
  
      $attrs.$observe('a', function(new_v){
        console.log(new_v);
      });
    }  
    return {compile: func,
            restrict: 'E'}
  });
  
  app.controller('TestCtrl', function($scope){
    $scope.a = 123;
  });

$set 方法的定義是: function(key, value, writeAttr, attrName) { ... } 。

  • key 對象的成員名。

  • value 須要設置的值。

  • writeAttr 是否同時修改 DOM 節點的屬性(注意區別「節點」與「對象」),默認爲 true

  • attrName 實際的屬性名,與「標準化」以後的屬性名有區別。

  <div ng-controller="TestCtrl">
    <test a="1" ys-a="123" ng-click="show(1)">這裏</test>
  </div>
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('test', function(){    var func = function($element, $attrs){
      $attrs.$set('b', 'ooo');
      $attrs.$set('a-b', '11');
      $attrs.$set('c-d', '11', true, 'c_d');
      console.log($attrs);
    }  
    return {compile: func,
            restrict: 'E'}
  });
  
  app.controller('TestCtrl', function($scope){
    $scope.show = function(v){console.log(v);}
  });

從例子中能夠看到,原始的節點屬性值對,放到對象中以後,名字必定是「標準化」以後的。可是手動$set 的新屬性,不會自動作標準化處理。

18.10. 預約義的 NgModelController

在前面講 conroller 參數的時候,提到過能夠爲指令定義一個 conroller 。官方的實現中,有不少已定義的指令,這些指令當中,有兩個已定義的 conroller ,它們是 NgModelController 和FormController ,對應 ng-model 和 form 這兩個指令(能夠參照前面的「表單控件」一章)。

在使用中,除了能夠經過 $scope 來取得它們的引用以外,也能夠在自定義指令中經過 require 參數直接引用,這樣就能夠在 link 函數中使用 controller 去實現一些功能。

先看 NgModelController 。這東西的做用有兩個,一是控制 ViewValue 與 ModelValue 之間的轉換關係(你能夠實現看到的是一個值,可是存到變量裏變成了另一個值),二是與FormController 配合作數據校驗的相關邏輯。

先看兩個應該是最有用的屬性:

  • $formatters 是一個由函數組成的列表,串行執行,做用是把變量值變成顯示的值。


  • $parsers 與上面的方向相反,把顯示的值變成變量值。


假設咱們在變量中要保存一個列表的類型,可是顯示的東西只能是字符串,因此這二者之間須要一個轉換:

  <div ng-controller="TestCtrl">
    <input type="text" ng-model="a" test />
    <button ng-click="show(a)">查看</button>
  </div>
 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('test', function(){ 4     var link = function($scope, $element, $attrs, $ctrl){ 5    6       $ctrl.$formatters.push(function(value){ 7         return value.join(','); 8       }); 9   10       $ctrl.$parsers.push(function(value){11         return value.split(',');12       });13     }14   15     return {compile: function(){return link},16             require: 'ngModel',17             restrict: 'A'}18   });19   20   app.controller('TestCtrl', function($scope){21     $scope.a = [];22     //$scope.a = [1,2,3];23     $scope.show = function(v){24       console.log(v);25     }26   });

上面在定義 test 這個指令, require 參數指定了 ngModel 。同時由於 DOM 結構, ng-model是存在的。因而, link 函數中就能夠獲取到一個 NgModelController 的實例,即代碼中的 $ctrl

咱們添加了須要的過濾函數:

  • 從變量( ModelValue )到顯示值( ViewValue )的過程, $formatters 屬性,把一個列表變成一個字符串。

  • 從顯示值到變量的過程, $parsers 屬性,把一個字符串變成一個列表。

對於顯示值和變量,還有其它的 API ,這裏就不細說了。

另外一部分,是關於數據校驗的,放到下一章同 FormController 一塊兒討論。

18.11. 預約義的 FormController

前面的「表單控制」那章,實際上講的就是 FormController ,只是那裏是從 scope 中獲取到的引用。如今從指令定義的角度,來更清楚地瞭解 FormController 及 NgModelController 是如何配合工做的。

先說一下, form 和 ngForm 是官方定義的兩個指令,可是它們實際上是同一個東西。前者只容許以標籤形式使用,然後者容許 EAC 的形式。DOM 結構中, form 標籤不能嵌套,可是 ng 的指令沒有這個限制。無論是 form 仍是 ngForm ,它們的 controller 都被命名成了 form 。 因此require 這個參數不要寫錯了。

FormController 的幾個成員是很好理解的:

  • $pristine 表單是否被動過


  • $dirty 表單是否沒被動過


  • $valid 表單是否檢驗經過


  • $invalid 表單是否檢驗未經過


  • $error 表單中的錯誤


  • $setDirty() 直接設置 $dirty 及 $pristine


  <div ng-controller="TestCtrl">
    <div ng-form test>
      <input ng-model="a" type="email" />
      <button ng-click="do()">查看</button>
    </div>
  </div>
  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('test', function(){    var link = function($scope, $element, $attrs, $ctrl){
      $scope.do = function(){        //$ctrl.$setDirty();
        console.log($ctrl.$pristine); //form是否沒被動過
        console.log($ctrl.$dirty); //form是否被動過
        console.log($ctrl.$valid); //form是否被檢驗經過
        console.log($ctrl.$invalid); //form是否有錯誤
        console.log($ctrl.$error); //form中有錯誤的字段
      }
    }  
    return {compile: function(){return link},
            require: 'form',
            restrict: 'A'}
  });
  
  app.controller('TestCtrl', function($scope){
  });

$error 這個屬性,是一個對象, key 是錯誤名, value 部分是一個列表,其成員是對應的NgModelController 的實例。

FormController 能夠自由增減它包含的那些,相似於 NgModelController 的實例。在 DOM 結構上,有 ng-model 的 input 節點的 NgMoelController 會被自動添加。

  • $addControl() 添加一個 conroller


  • $removeControl() 刪除一個 controller


這兩個手動使用機會應該不會不少。被添加的實例也能夠手動實現全部的 NgModelController 的方法

  <div ng-controller="TestCtrl">
    <bb />
    <div ng-form test>
      <input ng-model="a" type="email" />
      <button ng-click="add()">添加</button>
    </div>
  </div>
 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('test', function(){ 4     var link = function($scope, $element, $attrs, $ctrl){ 5       $scope.add = function(){ 6         $ctrl.$addControl($scope.bb); 7         console.log($ctrl); 8       } 9     }10   11     return {compile: function(){return link},12             require: 'form',13             restrict: 'A'}14   });15   16   app.directive('bb', function(){17     var controller = function($scope, $element, $attrs, $transclude){18       $scope.bb = this;19       this.$name = 'bb';20     }21   22     return {compile: angular.noop,23             restrict: 'E',24             controller: controller}25   });26   27   app.controller('TestCtrl', function($scope){28   });

整合 FormController 和 NgModelController 就很容易擴展各類類型的字段:

  <div ng-controller="TestCtrl">
    <form name="f">
      <input type="my" ng-model="a" />
      <button ng-click="show()">查看</button>
    </form>
  </div>
 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('input', function(){ 4     var link = function($scope, $element, $attrs, $ctrl){ 5       console.log($attrs.type); 6       var validator = function(v){ 7         if(v == '123'){ 8           $ctrl.$setValidity('my', true); 9           return v;10         } else {11           $ctrl.$setValidity('my', false);12           return undefined;13         }14       }15   16       $ctrl.$formatters.push(validator);17       $ctrl.$parsers.push(validator);18     }19   20     return {compile: function(){return link},21             require: 'ngModel',22             restrict: 'E'}23   });24   25   app.controller('TestCtrl', function($scope){26       $scope.show = function(){27         console.log($scope.f);28       }29   });

雖然官方原來定義了幾種 type ,但這不妨礙咱們繼續擴展新的類型。若是新的 type 參數值不在官方的定義列表裏,那會按 text 類型先作處理,這其實什麼影響都沒有。剩下的,就是寫咱們本身的驗證邏輯就好了。

上面的代碼是參見官方的作法,使用格式化的過程,同時在裏面作有效性檢查。

18.12. 示例:文本框

這個例子與官網上的那個例子類似。最終是要顯示一個文本框,這個文本框由標題和內容兩部分組成。並且標題和內容則是引用 controller 中的變量值。

HTML 部分的代碼:

  <div ng-controller="TestCtrl">
    <ys-block title="title" text="text"></ys-block>
    <p>標題: <input ng-model="title" /></p>
    <p>內容: <input ng-model="text" /></p>
    <ys-block title="title" text="text"></ys-block>
  </div>

從這個指望實現效果的 HTML 代碼中,咱們能夠考慮設計指令的實現方式:

  • 這個指令的使用方式是「標籤」, 即 restrict 這個參數應該設置爲 E 。

  • 節點的屬性值是對 controller 變量的引用,那麼咱們應該在指令的 scope 中使用 = 的方式來指定成員值。

  • 最終的效果顯示須要進行 DOM 結構的重構,那直接使用 template 就行了。

  • 自定義的標籤在最終效果中是多餘的,全部 replace 應該設置爲 true 。

JS 部分的代碼:

  var app = angular.module('Demo', [], angular.noop);
  
  app.directive('ysBlock', function(){    return {compile: angular.noop,
            template: '<div style="width: 200px; border: 1px solid black;"><h1 style="background-color: gray; color: white; font-size: 22px;">{{ title }}</h1><div>{{ text }}</div></div>',
            replace: true,
            scope: {title: '=title', text: '=text'},
            restrict: 'E'};
  });
  
  app.controller('TestCtrl', function($scope){
    $scope.title = '標題在這裏';
    $scope.text = '內容在這裏';
  });
  
  angular.bootstrap(document, ['Demo']);

能夠看到,這種簡單的組件式指令,只須要做 DOM 結構的變換便可實現,連 compile 函數都不須要寫。

18.13. 示例:模板控制語句 for

這個示例嘗試實現一個重複語句,功能同官方的 ngRepeat ,可是使用方式相似於咱們一般編程語言中的 for 語句:

  <div ng-controller="TestCtrl" ng-init="obj_list=[1,2,3,4]; name='name'">
    <ul>
      <for o in obj_list>
        <li>{{ o }}, {{ name }}</li>
      </for>
    </ul>
    <button ng-click="obj_list=[1,2]; name='o?'">修改</button>
  </div>

一樣,咱們從上面的使用方式去考慮這個指令的實現:

  • 這是一個徹底的控制指令,因此單個節點應該只有它一個指令起做用就行了,因而權重要比較高,而且「到此爲止」—— priority 設置爲 1000 , terminal 設置爲 true 。

  • 使用時的語法問題。事實上瀏覽器會把 for 節點補充成一個正確的 HTML 結構,即裏面的屬性都會變成相似 o="" 這樣。咱們經過節點的 outerHTML 屬性取到字符串並解析取得須要的信息。

  • 咱們把 for 節點之間的內容做爲一個模板,而且經過循環屢次渲染該模板以後把結果填充到合適的位置。

  • 在處理上面的那個模板時,須要不斷地建立新 scope 的,而且 o 這個成員須要單獨賦值。

注意:這裏只是簡單實現功能。官方的那個 ngRepeat 比較複雜,是作了專門的算法優化的。固然,這裏的實現也能夠是簡單把 DOM 結構變成使用 ngRepeat 的形式 :)

JS 部分代碼:

 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('for', function($compile){ 4     var compile = function($element, $attrs, $link){ 5       var match = $element[0].outerHTML.match('<for (.*?)=.*? in=.*? (.*?)=.*?>'); 6       if(!match || match.length != 3){throw Error('syntax: <for o in obj_list>')} 7       var iter = match[1]; 8       var list = match[2]; 9       var tpl = $compile($.trim($element.html()));10       $element.empty();11   12       var link = function($scope, $ielement, $iattrs, $controller){13   14         var new_node = [];15   16         $scope.$watch(list, function(list){17           angular.forEach(new_node, function(n){n.remove()});18           var scp, inode;19           for(var i = 0, ii = list.length; i < ii; i++){20             scp = $scope.$new();21             scp[iter] = list[i];22             inode = tpl(scp, angular.noop);23             $ielement.before(inode);24             new_node.push(inode);25           }26   27         });28       }29   30       return link;31     }32     return {compile: compile,33             priority: 1000,34             terminal: true,35             restrict: 'E'};36   });37   38   app.controller('TestCtrl', angular.noop);39   angular.bootstrap(document, ['Demo']);

18.14. 示例:模板控制語句 if/else

這個示例是嘗試實現:

  <div ng-controller="TestCtrl">
    <if true="a == 1">
        <p>判斷爲真, {{ name }}</p>
      <else>
        <p>判斷爲假, {{ name }}</p>
      </else>
    </if>
  
    <div>
      <p>a: <input ng-model="a" /></p>
      <p>name: <input ng-model="name" /></p>
    </div>
  </div>

考慮實現的思路:

  • else 與 if 是兩個指令,它們是父子關係。經過 scope 能夠聯繫起來。至於 scope 是在link 中處理仍是 controller 中處理並不重要。

  • true 屬性的條件判斷經過 $parse 服務很容易實現。

  • 若是最終效果要去掉 if 節點,咱們可使用註釋節點來「佔位」。

JS 代碼:

 1   var app = angular.module('Demo', [], angular.noop); 2    3   app.directive('if', function($parse, $compile){ 4     var compile = function($element, $attrs){ 5       var cond = $parse($attrs.true); 6        7       var link = function($scope, $ielement, $iattrs, $controller){ 8         $scope.if_node = $compile($.trim($ielement.html()))($scope, angular.noop); 9         $ielement.empty();10         var mark = $('<!-- IF/ELSE -->');11         $element.before(mark);12         $element.remove();13   14         $scope.$watch(function(scope){15           if(cond(scope)){16             mark.after($scope.if_node);17             $scope.else_node.detach();18           } else {19             if($scope.else_node !== undefined){20               mark.after($scope.else_node);21               $scope.if_node.detach();22             }23           }24         });25       }26       return link;27     }28   29     return {compile: compile,30             scope: true,31             restrict: 'E'}32   });33   34   app.directive('else', function($compile){35     var compile = function($element, $attrs){36       37       var link = function($scope, $ielement, $iattrs, $controller){38         $scope.else_node = $compile($.trim($ielement.html()))($scope, angular.noop);39         $element.remove();40       }41       return link;42     }43   44     return {compile: compile,45             restrict: 'E'}46   });47   48   app.controller('TestCtrl', function($scope){49     $scope.a = 1;50   });51   52   angular.bootstrap(document, ['Demo']);

代碼中注意一點,就是 if_node 在獲得之時,就已是作了變量綁定的了。錯誤的思路是,在$watch 中再去不斷地獲得新的 if_node 。

評論

©2010-2014 zouyesheng.com All rights reserved. Powered by GitHub , txt2tags , MathJax

相關文章
相關標籤/搜索