一、有感而發的一些話
在學習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組件出來,這樣才算是學會了。
今天爬香山回來,累的夠嗆,時辰不早,收工睡覺~