一、有感而發的一些話
在學習ng之前有聽前輩說過,angular上手比較難,初學者可能不太適應其語法以及思想。隨著對ng探索的一步步深入,也確實感覺到了這一點,尤其是框架內部的某些執行機制,其複雜程度並非是我現在的功力能夠理解的,只能是知其皮毛。我現在學習的途徑是官方文件 + AngularJS在github上的中文粗譯版(https://github.com/basestyle/angularjs-cn)+ 網上搜到的一些文章。鑑於本人資質平平以前也只用過jQuery,目前只能做到理解ng的API文件,相關特性的使用方式。故部落格的主要內容也就是記載一些我的理解與應用,對ng框架內部的機制只做必要的瞭解,暫不深入探究。
智商捉急,就到這裡~
接下來聊聊angular的指令機制。angular通過指令的方式實現了HTML的擴充套件,增強後的HTML就好比是究極進化後的暴龍獸,不僅長相煥然一新,同時也獲得了很多強大的技能。更厲害的是,你還可以自定義指令,這就意味著HTML標籤的範圍可以擴充套件到無窮大,ng賦予了你造物主的能力。作為angular的精華之一,指令相關的知識也很多,本篇開始探索自定義指令的方方面面。為了不讓我的篇幅再拉那麼長,我識趣的在標題後面加了(上),你懂的。
二、指令的編譯過程及命名方式
在開始自定義指令之前,我們有必要了解一下指令在框架中的執行流程。這部分內容我沒有自己研究,只是照搬了別人的說法:
- 瀏覽器得到 HTML 字串內容,解析得到 DOM 結構。
- ng 引入,把 DOM 結構扔給 $compile 函式處理:
① 找出 DOM 結構中有變數佔位符
② 匹配找出 DOM 中包含的所有指令引用
③ 把指令關聯到 DOM
④ 關聯到 DOM 的多個指令按權重排列
⑤ 執行指令中的 compile 函式(改變 DOM 結構,返回 link 函式)
⑥ 得到的所有 link 函式組成一個列表作為 $compile 函式的返回
3. 執行 link 函式(連線模板的 scope)。
這裡注意區別一下$compile和compile,前者是ng內部的編譯服務,後者是指令中的編譯函式,兩者發揮作用的範圍不同。compile和link函式息息相關又有所區別,這個在後面會講。瞭解執行流程對後面的理解會有幫助。
在這裡我小小的多嘴一下,有些人可能會問,angular不就是一個js框架嗎,怎麼還能跟編譯扯上呢,又不是像C++那樣的高階語言。其實此編譯非彼編譯,ng編譯的工作是解析指令啦,繫結監聽器啦,替換模板中的變數啦這些。因為工作方式很像高階語言編輯中的遞迴、堆疊過程,所以起名為編譯,不要疑惑。
指令的幾種使用方式如下:
作為標籤:<my-dir></my-dir>
作為屬性:<span my-dir="exp"></span>
作為註釋:<!-- directive: my-dir exp -->
作為類名:<span class="my-dir: exp;"></span>
其實常用的就是作為標籤和屬性,下面兩種用法目前還沒見過,姑且留個印象。我們自定義的指令就是要支援這樣的用法。
關於自定義指令的命名,你可以隨便怎麼起名字都行,官方是推薦用[名稱空間-指令名稱]這樣的方式,像ng-controller。不過你可千萬不要用ng-字首了,防止與系統自帶的指令重名。另外一個需知道的地方,指令命名時用駝峰規則,使用時用-分割各單詞。如:定義myDirective,使用時像這樣:<my-directive>。
三、自定義指令的配置引數
下面是定義一個標準指令的示例,可配置的引數包括以下部分:
myModule.directive('namespaceDirectiveName', function factory(injectables) { var directiveDefinitionObject = { restrict: string,//指令的使用方式,包括標籤,屬性,類,註釋 priority: number,//指令執行的優先順序 template: string,//指令使用的模板,用HTML字串的形式表示 templateUrl: string,//從指定的url地址載入模板 replace: bool,//是否用模板替換當前元素,若為false,則append在當前元素上 transclude: bool,//是否將當前元素的內容轉移到模板中 scope: bool or object,//指定指令的作用域 controller: function controllerConstructor($scope, $element, $attrs, $transclude){...},//定義與其他指令進行互動的介面函式 require: string,//指定需要依賴的其他指令 link: function postLink(scope, iElement, iAttrs) {...},//以程式設計的方式操作DOM,包括新增監聽器等 compile: function compile(tElement, tAttrs, transclude){ return: { pre: function preLink(scope, iElement, iAttrs, controller){...}, post: function postLink(scope, iElement, iAttrs, controller){...} } }//程式設計的方式修改DOM模板的副本,可以返回連結函式 }; return directiveDefinitionObject; });
看上去好複雜的樣子啊~定義一個指令需要這麼多步驟嘛?當然不是,你可以根據自己的需要來選擇使用哪些引數。事實上priority和compile用的比較少,template和templateUrl又是互斥的,兩者選其一即可。所以不必緊張,接下來分別學習一下這些引數,我將先從一個簡單的例子開始。為了易於理解和我以後翻看的時候還能看明白,我儘量使用有語義的命名,而不是像test1,test2這樣。
例子的程式碼如下:
var app = angular.module('MyApp', [], function(){console.log('here')});
app.directive('sayHello',function(){ return { restrict : 'E', template : '<div>hello</div>' }; })
然後在頁面中,我們就可以使用這個名為sayHello的指令了,它的作用就是輸出一個hello單詞。像這樣使用:
<say-hello></say-hello>
這樣頁面就會顯示出hello了,看一下生成的程式碼:
稍稍解釋一下我們用到的兩個引數,restirct用來指定指令的使用型別,其取值及含義如下:
取值 |
含義 |
使用示例 |
E |
標籤 |
<my-menu title=Products></my-menu> |
A |
屬性 |
<div my-menu=Products></div> |
C |
類 |
<div class="my-menu":Products></div> |
M |
註釋 |
<!--directive:my-menu Products--> |
預設值是A。也可以使用這些值的組合,如EA,EC等等。我們這裡指定為E,那麼它就可以像標籤一樣使用了。如果指定為A,我們使用起來應該像這樣:
<div say-hello></div>
從生成的程式碼中,你也看到了template的作用,它就是描述你的指令長什麼樣子,這部分內容將出現在頁面中,即該指令所在的模板中,既然是模板中,template的內容中也可以使用ng-modle等其他指令,就像在模板中使用一樣。
在上面生成的程式碼中,我們看到了<div>hello</div>外面還包著一層<say-hello>標籤,如果我們不想要這一層多餘的東西了,replace就派上用場了,在配置中將replace賦值為true,將得到如下結構:
replace的作用正如其名,將指令標籤替換為了temple中定義的內容。不寫的話預設為false。
上面的template未免也太簡單了,如果你的模板HTML較複雜,如自定義一個ui元件指令,難道要拼接老長的字串?當然不需要,此時只需用templateUrl便可解決問題。你可以將指令的模板單獨命名為一個html檔案,然後在指令定義中使用templateUrl指定好檔案的路徑即可,如:
templateUrl : ‘helloTemplate.html’
系統會自動發一個http請求來獲取到對應的模板內容。是不是很方便呢,你不用糾結於拼接字串的煩惱了。如果你是一個追求完美的有考慮效能的工程師,可能會發問:那這樣的話豈不是要犧牲一個http請求?
這也不用擔心,因為ng的模板還可以用另外一種方式定義,那就是使用<script>標籤。使用起來如下:
<script type="text/ng-template" id="helloTemplate.html"> <div>hello</div> </script>
你可以把這段程式碼寫在頁面頭部,這樣就不必去請求它了。在實際專案中,你也可以將所有的模板內容集中在一個檔案中,只載入一次,然後根據id來取用。
接下來我們來看另一個比較有用的配置:transclude,定義是否將當前元素的內容轉移到模板中。看解釋有點抽象,不過親手試試就很清楚了,看下面的程式碼:
app.directive('sayHello',function(){ return { restrict : 'E', template : '<div>hello,<b ng-transclude></b></div>', replace : true, transclude : true }; })
指定了transclude為true,並且template修改了一下,加了一個<b>標籤,並在上面使用了ng-transclude指令,用來告訴指令把內容轉移到的位置。那我們要轉移的內容是什麼呢?請看使用指令時的變化:
<say-hello>美女</say-hello>
內容是什麼你也看到了哈~在執行的時候,美女將會被轉移到<b>標籤中,原來此配置的作用就是——乾坤大挪移!看效果:
這個還是很有用的,因為你定義的指令不可能老是那麼簡單,只有一個空標籤。當你需要對指令中的內容進行處理時,此引數便大有可用。
四、結束
看前面寫的兩篇,感覺篇幅太長了,可能會有人耐不住性子看完,故本篇先介紹幾個比較簡單的引數,先拿軟的來捏一捏,更復雜的用法還在後頭。我們將真正用一下自定義指令,起碼也搞個像樣的ui元件出來,這樣才算是學會了。
今天爬香山回來,累的夠嗆,時辰不早,收工睡覺~