走進AngularJs(三)自定義指令-----(上)

呂大豹發表於2013-10-28

一、有感而發的一些話

  在學習ng之前有聽前輩說過,angular上手比較難,初學者可能不太適應其語法以及思想。隨著對ng探索的一步步深入,也確實感覺到了這一點,尤其是框架內部的某些執行機制,其複雜程度並非是我現在的功力能夠理解的,只能是知其皮毛。我現在學習的途徑是官方文件 + AngularJS在github上的中文粗譯版(https://github.com/basestyle/angularjs-cn)+ 網上搜到的一些文章。鑑於本人資質平平以前也只用過jQuery,目前只能做到理解ng的API文件,相關特性的使用方式。故部落格的主要內容也就是記載一些我的理解與應用,對ng框架內部的機制只做必要的瞭解,暫不深入探究。

  智商捉急,就到這裡~

  接下來聊聊angular的指令機制。angular通過指令的方式實現了HTML的擴充套件,增強後的HTML就好比是究極進化後的暴龍獸,不僅長相煥然一新,同時也獲得了很多強大的技能。更厲害的是,你還可以自定義指令,這就意味著HTML標籤的範圍可以擴充套件到無窮大,ng賦予了你造物主的能力。作為angular的精華之一,指令相關的知識也很多,本篇開始探索自定義指令的方方面面。為了不讓我的篇幅再拉那麼長,我識趣的在標題後面加了(上),你懂的。

二、指令的編譯過程及命名方式

  在開始自定義指令之前,我們有必要了解一下指令在框架中的執行流程。這部分內容我沒有自己研究,只是照搬了別人的說法:

  1. 瀏覽器得到 HTML 字串內容,解析得到 DOM 結構。
  2. 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元件出來,這樣才算是學會了。

  今天爬香山回來,累的夠嗆,時辰不早,收工睡覺~

相關文章