2014-01-22 21:48 更新javascript
鄒業盛css
關於AngularJShtml
關於本文檔前端
2014-01-22 21:48 更新java
鄒業盛node
關於AngularJSjquery
關於本文檔git
開始的例子angularjs
依賴注入github
本文的內容是在 1.0.x 版本之下完成的。
AngularJS 是 Google 開源出來的一套 js 工具。下面簡稱其爲 ng 。這裏只說它是「工具」,沒說它是完整的「框架」,是由於它並非定位於去完成一套框架要作的事。更重要的,是它給咱們揭示了一種新的應用組織與開發方式。
ng 最讓我稱奇的,是它的數據雙向綁定。其實想一想,咱們一直在提數據與表現的分離,可是這裏的「雙向綁定」從某方面來講,是把數據與表現徹底綁定在一塊兒——數據變化,表現也變化。反之,表現變化了,內在的數據也變化。有過開發經驗的人能體會到這種機制對於前端應用來講,是頗有必要的,能帶來維護上的巨大優點。固然,這裏的綁定與提倡的分離並非矛盾的。
ng 能夠和 jQuery 集成工做,事實上,若是沒有 jQuery , ng 本身也作了一個輕量級的 jQuery ,主要實現了元素操做部分的 API 。
關於 ng 的幾點:
對 IE 方面,它兼容 IE8 及以上的版本。
與 jQuery 集成工做,它的一些對象與 jQuery 相關對象表現是一致的。
使用 ng 時不要冒然去改變相關 DOM 的結構。
這份文檔如其名,是我本身學習 ng 的過程記錄。只是過程記錄,沒有刻意像教程那樣去作。因此呢,從前至後,中間難免有一些概念不清不明的地方。由於事實上,在某個階段對於一些概念原本就不可能明白。因此,整個過程只求在形式上的能用便可——直到最後的「自定義」那幾章,特別是「自定義指令」,那幾章過完,你才能看清 ng 原本的面貌。前面就不要太糾結概念,本質,知道怎麼用就好。
咱們從一個完整的例子開始認識 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
來顯示地作初始化工具,參數指明瞭根節點,裝載的模塊(能夠是多個模塊)。
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)
這裏提到的「做用域」的概念,是一個在範圍上與 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 的數據綁定機制,操做變量的結果直接在頁面上表現出來了。
我糾結了半天,「數據綁定」與「模板」這兩個東西還真沒辦法分開來講。由於數據綁定須要以模板爲載體,離開了模板,數據還綁個毛啊。
ng 的一大特色,就是數據雙向綁定。雙向綁定是一體,爲了描述方便,下面分別介紹。
數據到表現的綁定,主要是使用模板標記直接完成的:
<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();
模板到數據的綁定,主要是經過 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 裏,還要區分對待不一樣的控件。
如今要考慮的是一種在現實中很廣泛的一個需求。好比就是咱們能夠輸入數值,來控制一個矩形的長度。在這裏,數據與表現的關係是:
長度數值保存在變量中
變量顯示於某個 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>
前面講了數據綁定以後,如今能夠單獨講講模板了。
做爲一套能稱之謂「模板」的系統,除了能幹一些模板的常規的事以外(好吧,即便是常規的邏輯判斷如今它也作不了的),配合做用域 $scope 和 ng 的數據雙向綁定機制, ng 的模板系統就變得比較神奇了。
定義模板的內容如今有三種方式:
在須要的地方直接寫字符串
外部文件
使用 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>
這算是惟一的一個控制標籤麼……,它的使用方法類型於:
<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'}]; }
這個指令能夠在模板中直接賦值,它做用於 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>
可使用一個結構直接表示當前節點的樣式:
<div ng-style="{width: 100 + 'px', height: 100 + 'px', backgroundColor: 'red'}"> </div>
一樣地,綁定一個變量的話,威力大了。
就是直接地設置當前節點的類,一樣,配合數據綁定做用就大了:
<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>
注意裏面給的仍是表示式,別少了引號。
前兩個是控制 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>
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
。
事件綁定是模板指令中很好用的一部分。咱們能夠把相關事件的處理函數直接寫在 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>
表單控件類的模板指令,最大的做用是它預約義了須要綁定的數據的格式。這樣,就能夠對於既定的數據進行既定的處理。
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); } }
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
錯誤類型。
它也算是 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'; }
兩點:
controller 要初始化變量值。
controller 中的初始化值會關係到控件狀態(雙向綁定)。
也是 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>
同 input 。
這是一個比較牛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>
這裏說的過濾器,是用於對數據的格式化,或者篩選的函數。它們能夠直接在模板中經過一種語法使用。對於經常使用功能來講,是很方便的一種機制。
多個過濾器之間能夠直接連續使用。
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>
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>
時間戳格式化 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 }}
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>
<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>
準確地說,這應該叫對 hashchange 事件的處理吧。
就是指 URL 中的錨點部分發生變化時,觸發預先定義的業務邏輯。好比如今是 /test#/x
,錨點部分的值爲 #
後的 /x
,它就對應了一組處理邏輯。當這部分變化時,好比變成了 /test#/t
,這時頁面是不會刷新的,可是它能夠觸發另一組處理邏輯,來作一些事,也可讓頁面發生變化。
這種機制對於複雜的單頁面來講,無疑是一種強大的業務切分手段。就算不是複雜的單頁面應用,在普通頁面上善用這種機制,也可讓業務邏輯更容易控制。
ng 提供了完善的錨點路由功能,雖然目前我以爲至關重要的一個功能還有待完善(後面會說),但目前這功能的幾部份內容,已經讓我思考了不少種可能性了。
ng 中的錨點路由功能是由幾部分 API 共同完成的一整套方案。這其中包括了路由定義,參數定義,業務處理等。
要使用錨點路由功能,須要在先定義它。目前,對於定義的方法,我我的只發如今「初始化」階段能夠經過 $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' } ); } ]);
在做路由定義時,能夠匹配一個規則,規則中能夠定義路徑中的某些部分做爲參數之用,而後使用$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
簡單來講,當一個錨點路由定義被匹配時,會根據模板生成一個 $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 不會被觸發。
因爲下面涉及動態內容,因此我打算起一個後端服務來作。可是我發現我使用的 Tornado 框架的模板系統,與 ng 的模板系統,都是使用 {{ }}
這對符號來定義模板表達式的,這太悲劇了,不過幸虧 ng 已經提供了修改方法:
angular.bootstrap(document.documentElement, [function($interpolateProvider){ $interpolateProvider.startSymbol('[['); $interpolateProvider.endSymbol(']]'); }]);
使用 $interpolateProvider 服務便可。
ng 提供了基本的 AJAX 封裝,你直接面對 promise 對象,使用起來仍是很方便的。
基本的操做由 $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);
和其它框架同樣, 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 這三個東西。
$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)} );
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');
promise 對象只有 then() 一個方法,註冊成功回調函數和失敗回調函數,再返回一個 promise 對象,以用於鏈式調用。
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')();
對象複製: 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);
angular.isArray
angular.isDate
angular.isDefined
angular.isElement
angular.isFunction
angular.isNumber
angular.isObject
angular.isString
angular.isUndefined
ng 提供 $log 這個服務用於向終端輸出相關信息:
error()
info()
log()
warn()
var TestCtrl = function($log){ $log.error('error'); $log.info('info'); $log.log('log'); $log.warn('warn'); }
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 實例。
$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() 能夠取消計時器。
$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);
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); } }
總的來講,模塊是組織業務的一個框框,在一個模塊當中定義多個服務。當你引入了一個模塊的時候,就可使用這個模塊提供的一種或多種服務了。
好比 AngularJS 自己的一個默認模塊叫作 ng ,它提供了 $http , $q 等等服務。
服務只是模塊提供的多種機制中的一種,其它的還有命令( directive ),過濾器( filter ),及其它配置信息。
而後在額外的 js 文件中有一個附加的模塊叫作 ngResource , 它提供了一個 $resource 服務。
定義時,咱們能夠在已有的模塊中新定義一個服務,也能夠先新定義一個模塊,而後在新模塊中定義新服務。
使用時,模塊是須要顯式地的聲明依賴(引入)關係的,而服務則可讓 ng 自動地作注入,而後直接使用。
定義模塊的方法是使用 angular.module
。調用時聲明瞭對其它模塊的依賴,並定義了「初始化」函數。
var my_module = angular.module('MyModule', [], function(){ console.log('here'); });
這段代碼定義了一個叫作 MyModule
的模塊, my_module
這個引用能夠在接下來作其它的一些事,好比定義服務。
服務自己是一個任意的對象。可是 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()
爲例,過程至關於:
建立一個空對象 obj
把 obj 綁定到 a 函數的上下文當中(即 a 中的 this 如今指向 obj )
執行 a 函數
返回 obj
service 方法的使用就很簡單了:
var app = angular.module('Demo', [], function(){ }); app.service('PP', function(){ this.abc = '123'; }); app.controller('TestCtrl', function($scope, PP){ console.log(PP) });
結合上面的「定義模塊」和「定義服務」,咱們能夠方便地組織本身的額外代碼:
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()) });
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 分紅了「類」與「實例」這兩個層面。通常地,類的方法調用就是直觀的調用形式,一般會返回一個對象,這個對象即爲「實例」。
「實例」貫穿整個服務的使用過程。「實例」的數據是填充方式,即由於異步關係,回調函數沒有執行時,實例已經存在,只是可能它尚未相關數據,回調執行以後,相關數據被填充到實例對象當中。實例的方法通常就是在類方法名前加一個 $ ,調用上,根據定義,實例數據可能會作一些自動的參數填充,這點是區別實例與類的調用上的不一樣。
好吧,上面這些話可能須要在看了接下來的內容以後再回過來理解。
就像使用 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, 返回的數據是否是一個列表
在定義了資源以後,咱們看若是使用這些資源,發出請求:
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();
兩方面。一是在定義時,在其 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
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; });
這個問題彷佛不少人都關心,可是事實是,若是瞭解了 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 結構,但這裏說兩點:
對於整個頁面,你能夠只在局部使用 ng ,不使用 ng 的地方你能夠隨意控制 DOM 。
若是 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>
先來回顧一下 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>
這是 ng 最強大的一部分,也是最複雜最讓人頭疼的部分。
目前咱們看到的所謂「模板」系統,只不過是官方實現的幾個指令而已。這意味着,經過自定義各類指令,咱們不但能夠徹底定義一套「模板」系統,更能夠把 HTML 頁面直接打形成爲一種 DSL (領域特定語言)。
使用指令時,它的名字能夠有多種形式,把指令放在什麼地方也有多種選擇。
一般,指令的定義名是形如 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 的結構當中真算得上是一種神奇的機制。
ng 中對指令的解析與執行過程是這樣的:
瀏覽器獲得 HTML 字符串內容,解析獲得 DOM 結構。
ng 引入,把 DOM 結構扔給 $compile
函數處理:
找出 DOM 結構中有變量佔位符
匹配找出 DOM 中包含的全部指令引用
把指令關聯到 DOM
關聯到 DOM 的多個指令按權重排列
執行指令中的 compile
函數(改變 DOM 結構,返回 link
函數)
獲得的全部 link
函數組成一個列表做爲 $compile
函數的返回
執行 link
函數(鏈接模板的 scope
)。
自定義一個指令能夠很是很是的複雜,可是其基本的調用形式,同自定義服務大概是相同的:
<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> </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 }}
,那上面的代碼就不行了。這種狀況麻煩得多。後面會討論。
官方代碼中的 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>
指令的處理過程,是 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
執行它你就獲得了一個節點。
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'}; });
回顧最開始的那個代碼顯示的例子,那個例子只能處理一次節點內容。若是節點的內容是一個變量的話,須要用另外的思路來考慮。這裏咱們假設的例子是,定義一個指令 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
,以更新這個長度值。
指令定義時的參數以下:
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 });
節點屬性被包裝以後會傳給 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
的新屬性,不會自動作標準化處理。
在前面講 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 一塊兒討論。
前面的「表單控制」那章,實際上講的就是 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
類型先作處理,這其實什麼影響都沒有。剩下的,就是寫咱們本身的驗證邏輯就好了。
上面的代碼是參見官方的作法,使用格式化的過程,同時在裏面作有效性檢查。
這個例子與官網上的那個例子類似。最終是要顯示一個文本框,這個文本框由標題和內容兩部分組成。並且標題和內容則是引用 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
函數都不須要寫。
這個示例嘗試實現一個重複語句,功能同官方的 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']);
這個示例是嘗試實現:
<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
本文的內容是在 1.0.x 版本之下完成的。
AngularJS 是 Google 開源出來的一套 js 工具。下面簡稱其爲 ng 。這裏只說它是「工具」,沒說它是完整的「框架」,是由於它並非定位於去完成一套框架要作的事。更重要的,是它給咱們揭示了一種新的應用組織與開發方式。
ng 最讓我稱奇的,是它的數據雙向綁定。其實想一想,咱們一直在提數據與表現的分離,可是這裏的「雙向綁定」從某方面來講,是把數據與表現徹底綁定在一塊兒——數據變化,表現也變化。反之,表現變化了,內在的數據也變化。有過開發經驗的人能體會到這種機制對於前端應用來講,是頗有必要的,能帶來維護上的巨大優點。固然,這裏的綁定與提倡的分離並非矛盾的。
ng 能夠和 jQuery 集成工做,事實上,若是沒有 jQuery , ng 本身也作了一個輕量級的 jQuery ,主要實現了元素操做部分的 API 。
關於 ng 的幾點:
對 IE 方面,它兼容 IE8 及以上的版本。
與 jQuery 集成工做,它的一些對象與 jQuery 相關對象表現是一致的。
使用 ng 時不要冒然去改變相關 DOM 的結構。
這份文檔如其名,是我本身學習 ng 的過程記錄。只是過程記錄,沒有刻意像教程那樣去作。因此呢,從前至後,中間難免有一些概念不清不明的地方。由於事實上,在某個階段對於一些概念原本就不可能明白。因此,整個過程只求在形式上的能用便可——直到最後的「自定義」那幾章,特別是「自定義指令」,那幾章過完,你才能看清 ng 原本的面貌。前面就不要太糾結概念,本質,知道怎麼用就好。
咱們從一個完整的例子開始認識 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
來顯示地作初始化工具,參數指明瞭根節點,裝載的模塊(能夠是多個模塊)。
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)
這裏提到的「做用域」的概念,是一個在範圍上與 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 的數據綁定機制,操做變量的結果直接在頁面上表現出來了。
我糾結了半天,「數據綁定」與「模板」這兩個東西還真沒辦法分開來講。由於數據綁定須要以模板爲載體,離開了模板,數據還綁個毛啊。
ng 的一大特色,就是數據雙向綁定。雙向綁定是一體,爲了描述方便,下面分別介紹。
數據到表現的綁定,主要是使用模板標記直接完成的:
<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();
模板到數據的綁定,主要是經過 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 裏,還要區分對待不一樣的控件。
如今要考慮的是一種在現實中很廣泛的一個需求。好比就是咱們能夠輸入數值,來控制一個矩形的長度。在這裏,數據與表現的關係是:
長度數值保存在變量中
變量顯示於某個 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>
前面講了數據綁定以後,如今能夠單獨講講模板了。
做爲一套能稱之謂「模板」的系統,除了能幹一些模板的常規的事以外(好吧,即便是常規的邏輯判斷如今它也作不了的),配合做用域 $scope 和 ng 的數據雙向綁定機制, ng 的模板系統就變得比較神奇了。
定義模板的內容如今有三種方式:
在須要的地方直接寫字符串
外部文件
使用 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>
這算是惟一的一個控制標籤麼……,它的使用方法類型於:
<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'}]; }
這個指令能夠在模板中直接賦值,它做用於 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>
可使用一個結構直接表示當前節點的樣式:
<div ng-style="{width: 100 + 'px', height: 100 + 'px', backgroundColor: 'red'}"> </div>
一樣地,綁定一個變量的話,威力大了。
就是直接地設置當前節點的類,一樣,配合數據綁定做用就大了:
<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>
注意裏面給的仍是表示式,別少了引號。
前兩個是控制 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>
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
。
事件綁定是模板指令中很好用的一部分。咱們能夠把相關事件的處理函數直接寫在 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>
表單控件類的模板指令,最大的做用是它預約義了須要綁定的數據的格式。這樣,就能夠對於既定的數據進行既定的處理。
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); } }
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
錯誤類型。
它也算是 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'; }
兩點:
controller 要初始化變量值。
controller 中的初始化值會關係到控件狀態(雙向綁定)。
也是 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>
同 input 。
這是一個比較牛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>
這裏說的過濾器,是用於對數據的格式化,或者篩選的函數。它們能夠直接在模板中經過一種語法使用。對於經常使用功能來講,是很方便的一種機制。
多個過濾器之間能夠直接連續使用。
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>
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>
時間戳格式化 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 }}
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>
<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>
準確地說,這應該叫對 hashchange 事件的處理吧。
就是指 URL 中的錨點部分發生變化時,觸發預先定義的業務邏輯。好比如今是 /test#/x
,錨點部分的值爲 #
後的 /x
,它就對應了一組處理邏輯。當這部分變化時,好比變成了 /test#/t
,這時頁面是不會刷新的,可是它能夠觸發另一組處理邏輯,來作一些事,也可讓頁面發生變化。
這種機制對於複雜的單頁面來講,無疑是一種強大的業務切分手段。就算不是複雜的單頁面應用,在普通頁面上善用這種機制,也可讓業務邏輯更容易控制。
ng 提供了完善的錨點路由功能,雖然目前我以爲至關重要的一個功能還有待完善(後面會說),但目前這功能的幾部份內容,已經讓我思考了不少種可能性了。
ng 中的錨點路由功能是由幾部分 API 共同完成的一整套方案。這其中包括了路由定義,參數定義,業務處理等。
要使用錨點路由功能,須要在先定義它。目前,對於定義的方法,我我的只發如今「初始化」階段能夠經過 $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' } ); } ]);
在做路由定義時,能夠匹配一個規則,規則中能夠定義路徑中的某些部分做爲參數之用,而後使用$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
簡單來講,當一個錨點路由定義被匹配時,會根據模板生成一個 $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 不會被觸發。
因爲下面涉及動態內容,因此我打算起一個後端服務來作。可是我發現我使用的 Tornado 框架的模板系統,與 ng 的模板系統,都是使用 {{ }}
這對符號來定義模板表達式的,這太悲劇了,不過幸虧 ng 已經提供了修改方法:
angular.bootstrap(document.documentElement, [function($interpolateProvider){ $interpolateProvider.startSymbol('[['); $interpolateProvider.endSymbol(']]'); }]);
使用 $interpolateProvider 服務便可。
ng 提供了基本的 AJAX 封裝,你直接面對 promise 對象,使用起來仍是很方便的。
基本的操做由 $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);
和其它框架同樣, 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 這三個東西。
$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)} );
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');
promise 對象只有 then() 一個方法,註冊成功回調函數和失敗回調函數,再返回一個 promise 對象,以用於鏈式調用。
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')();
對象複製: 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);
angular.isArray
angular.isDate
angular.isDefined
angular.isElement
angular.isFunction
angular.isNumber
angular.isObject
angular.isString
angular.isUndefined
ng 提供 $log 這個服務用於向終端輸出相關信息:
error()
info()
log()
warn()
var TestCtrl = function($log){ $log.error('error'); $log.info('info'); $log.log('log'); $log.warn('warn'); }
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 實例。
$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() 能夠取消計時器。
$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);
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); } }
總的來講,模塊是組織業務的一個框框,在一個模塊當中定義多個服務。當你引入了一個模塊的時候,就可使用這個模塊提供的一種或多種服務了。
好比 AngularJS 自己的一個默認模塊叫作 ng ,它提供了 $http , $q 等等服務。
服務只是模塊提供的多種機制中的一種,其它的還有命令( directive ),過濾器( filter ),及其它配置信息。
而後在額外的 js 文件中有一個附加的模塊叫作 ngResource , 它提供了一個 $resource 服務。
定義時,咱們能夠在已有的模塊中新定義一個服務,也能夠先新定義一個模塊,而後在新模塊中定義新服務。
使用時,模塊是須要顯式地的聲明依賴(引入)關係的,而服務則可讓 ng 自動地作注入,而後直接使用。
定義模塊的方法是使用 angular.module
。調用時聲明瞭對其它模塊的依賴,並定義了「初始化」函數。
var my_module = angular.module('MyModule', [], function(){ console.log('here'); });
這段代碼定義了一個叫作 MyModule
的模塊, my_module
這個引用能夠在接下來作其它的一些事,好比定義服務。
服務自己是一個任意的對象。可是 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()
爲例,過程至關於:
建立一個空對象 obj
把 obj 綁定到 a 函數的上下文當中(即 a 中的 this 如今指向 obj )
執行 a 函數
返回 obj
service 方法的使用就很簡單了:
var app = angular.module('Demo', [], function(){ }); app.service('PP', function(){ this.abc = '123'; }); app.controller('TestCtrl', function($scope, PP){ console.log(PP) });
結合上面的「定義模塊」和「定義服務」,咱們能夠方便地組織本身的額外代碼:
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()) });
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 分紅了「類」與「實例」這兩個層面。通常地,類的方法調用就是直觀的調用形式,一般會返回一個對象,這個對象即爲「實例」。
「實例」貫穿整個服務的使用過程。「實例」的數據是填充方式,即由於異步關係,回調函數沒有執行時,實例已經存在,只是可能它尚未相關數據,回調執行以後,相關數據被填充到實例對象當中。實例的方法通常就是在類方法名前加一個 $ ,調用上,根據定義,實例數據可能會作一些自動的參數填充,這點是區別實例與類的調用上的不一樣。
好吧,上面這些話可能須要在看了接下來的內容以後再回過來理解。
就像使用 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, 返回的數據是否是一個列表
在定義了資源以後,咱們看若是使用這些資源,發出請求:
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();
兩方面。一是在定義時,在其 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
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; });
這個問題彷佛不少人都關心,可是事實是,若是瞭解了 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 結構,但這裏說兩點:
對於整個頁面,你能夠只在局部使用 ng ,不使用 ng 的地方你能夠隨意控制 DOM 。
若是 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>
先來回顧一下 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>
這是 ng 最強大的一部分,也是最複雜最讓人頭疼的部分。
目前咱們看到的所謂「模板」系統,只不過是官方實現的幾個指令而已。這意味着,經過自定義各類指令,咱們不但能夠徹底定義一套「模板」系統,更能夠把 HTML 頁面直接打形成爲一種 DSL (領域特定語言)。
使用指令時,它的名字能夠有多種形式,把指令放在什麼地方也有多種選擇。
一般,指令的定義名是形如 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 的結構當中真算得上是一種神奇的機制。
ng 中對指令的解析與執行過程是這樣的:
瀏覽器獲得 HTML 字符串內容,解析獲得 DOM 結構。
ng 引入,把 DOM 結構扔給 $compile
函數處理:
找出 DOM 結構中有變量佔位符
匹配找出 DOM 中包含的全部指令引用
把指令關聯到 DOM
關聯到 DOM 的多個指令按權重排列
執行指令中的 compile
函數(改變 DOM 結構,返回 link
函數)
獲得的全部 link
函數組成一個列表做爲 $compile
函數的返回
執行 link
函數(鏈接模板的 scope
)。
自定義一個指令能夠很是很是的複雜,可是其基本的調用形式,同自定義服務大概是相同的:
<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> </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 }}
,那上面的代碼就不行了。這種狀況麻煩得多。後面會討論。
官方代碼中的 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>
指令的處理過程,是 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
執行它你就獲得了一個節點。
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'}; });
回顧最開始的那個代碼顯示的例子,那個例子只能處理一次節點內容。若是節點的內容是一個變量的話,須要用另外的思路來考慮。這裏咱們假設的例子是,定義一個指令 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
,以更新這個長度值。
指令定義時的參數以下:
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 });
節點屬性被包裝以後會傳給 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
的新屬性,不會自動作標準化處理。
在前面講 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 一塊兒討論。
前面的「表單控制」那章,實際上講的就是 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
類型先作處理,這其實什麼影響都沒有。剩下的,就是寫咱們本身的驗證邏輯就好了。
上面的代碼是參見官方的作法,使用格式化的過程,同時在裏面作有效性檢查。
這個例子與官網上的那個例子類似。最終是要顯示一個文本框,這個文本框由標題和內容兩部分組成。並且標題和內容則是引用 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
函數都不須要寫。
這個示例嘗試實現一個重複語句,功能同官方的 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']);
這個示例是嘗試實現:
<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