AngularJS 自定義控件

AngularJS Custom Directivescss

好討厭不帶日期的博客,並且說得好囉嗦html


#自定義指令介紹angularjs

AngularJS 指令做用是在 AngulaJS 應用中操做 Html 渲染。好比說,內插指令 ( {{ }} ), ng-repeat 指令以及 ng-if 指令。app

固然你也能夠實現本身的。這就是 AngularJS 所謂的"教會 HTML 玩新姿式"。本文將告訴你如何作到。函數

#指令類型this

能夠自定義的指令類型以下:spa

  • 元素
  • 屬性
  • CSS class
  • Comment directives

這裏面,AngularJS 強烈建議你用元素和屬性類型,而不用 CSS class 和 comment directives (除非無可奈何)。翻譯

指令類型決定了指令什麼時候被激活。當 AngularJS 在 HTML 模板中找到一個 HTML 元素的時候,元素指令被激活。當 AngularJS 找到一個 HTML 元素屬性時,屬性指令被激活。當 AngularJS 找到一個 CSS class 時, CSS class 指令被激活,最後,當 AngularJS 找到 HTML comment 時,comment directive 被激活。rest

#基礎例子code

你能夠向模塊註冊一個指令,像這樣:

<!-- lang: js -->
myapp = angular.module("myapp", []);

myapp.directive('div', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */

    directive.template = "My first directive: {{textToInsert}}";

    return directive;
});

來看模塊中調用的 directive() 函數。當你調用該函數時,意味着你要註冊一個新指令。directive() 函數的第一個參數是新註冊指令的名稱。這是以後你在 HTML 模板中調用它的時候用的名稱。例子中,我用了 'div' ,意思是說當 HTML 模板每次在找到 HTML 元素類型是 div 的時候,這個指令都會被激活。

傳遞給 directive 函數的第二個參數是一個工廠函數。調用該函數會返回一個指令的定義。 AngularJS 經過調用該函數來獲取包含指令定義的 Javascript 對象。若是你有仔細看上面的例子,你會知道它返回的確是是一個 Javascript 對象。

這個從工廠函數中返回的 Javascript 對象有兩個屬性: restricttemplate 字段。

restrict 字段用來設置指令什麼時候被激活,是匹配到 HTML 元素時,仍是匹配到元素屬性時。也就是指令類型。把restrict 設置爲 E 時,只有名爲 div 的 HTML 元素纔會激活該指令。把 restrict 設置爲 A 時,只有名爲 div 的 HTML 元素屬性纔會激活該指令。你也能夠用 AE ,這樣會使得匹配到元素名和元素屬性名時,均可以激活該指令。

template 字段是一個 HTML 模板,用來替換匹配的 div 元素。它會把匹配到的 div 當成一個佔位符,而後用 HTML 模板在同一位置來替換掉它。也就是說,HTML 模板替換匹配的到 HTML 元素。

好比說你的 HTML 頁面有這樣一段 HTML:

<!-- lang: js -->
<div ng-controller="MyController" >
    <div>This div will be replaced</div>
</div>

當 AngularJS 找到內嵌的 div 的時候,會激活自定義的指令。而後把該 div 元素替換掉,替換後的 HTML 將變成這樣:

<!-- lang: js -->
My first directive: {{textToInsert}}

而後你看到了,這段 HTML 裏面包含了一個內插指令 ({{textToInsert}})。AngularJS 會再執行一次,讓內插指令實際值顯示出來。而後 $scope.textToInsert 屬性將會在這個 HTML 點上替換掉內插指令佔位符。

##template 和 templateUrl 屬性

建立自定義指令最簡單的例子就是上面這樣了。你的指令用來生成 HTML,而後你把 HTML 放到指令定義對象的 template 屬性中。下面這個例子是上面那最簡單的例子的重複,注意 template 部分:

<!-- lang: js -->
myapp = angular.module("myapp", []);

myapp.directive('div', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.template = "My first directive: {{textToInsert}}";

    return directive;
});

若是 HTML 模板愈來愈大,把它寫在一個 Javascript 字符串中確定很是難維護。你能夠把它放到一個單獨的文件中,而後 AngularJS 能夠經過這個文件把它加載進來。而後再把 HTML 模板文件的 URL 放到指令定義對象的 templateUrl 屬性中。下面是例子:

<!-- lang: js -->
myapp = angular.module("myapp", []);

myapp.directive('div', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.templateUrl = "/myapp/html-templates/div-template.html";

    return directive;
});

而後 AngularJS 會從 templateUrl 屬性中設置的 URL 將 HTML 模板加載進來。

用獨立的 HTML 模板文件,設置 templateUrl 屬性,在你要建立更多的通用指令時會顯得更加有用,好比說用來顯示用戶信息的指令。例子:

<!-- lang: js -->
myapp = angular.module("myapp", []);

myapp.directive('userinfo', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.templateUrl = "/myapp/html-templates/userinfo-template.html";

    return directive;
});

例子建立了一個指令,當AngularJS 找到一個 <userinfo> 元素的時候就會激活它。AngularJS 加載指向 /myapp/html-templates/userinfo-template.html 的模板並解析它,它就像從一開始就被嵌在上一級 HTML 文件中同樣。

#從指令中隔離 $scope

在上面的例子中,把 userinfo 指令硬綁定到了 $scope ,由於 HTML 模板是直接引用 textToInsert 屬性的。直接引用 $scope 讓指令在同一個 controller 中的時候,很是難複用,由於 $scope 在同一個 controller 中的值都是同樣的。好比說,你在頁面的 HTML 中這樣寫的時候:

<!-- lang: js -->
<userinfo></userinfo>
<userinfo></userinfo>

這兩個 <userinfo> 元素會被一樣的 HTML 模板取代,而後綁定到一樣的 $scope 變量上。結果就是兩個 <userinfo> 元素將會被如出一轍的 HTML 代碼給替換了。

爲了把兩個 <userinfo> 元素綁定到 $scope 中的不一樣的值,你須要給 HTML 模板一個 isolate scope

所謂 isolate scope 是綁定在指令上的獨立的 scope 對象。下面是定義的例子:

<!-- lang: js -->
myapp.directive('userinfo', function() {
    var directive = {};

    directive.restrict = 'E';

    directive.template = "User : {{user.firstName}} {{user.lastName}}";

    directive.scope = {
        user : "=user"
    }

    return directive;
})

請看 HTMl 模板中的兩個內插指令 {{user.firstName}}{{user.lastName}}。注意 user. 部分。以及directive.scope 屬性。 directive.scope 是個帶 user 屬性的 Javascript 對象。因而 directive.scope 就成爲了 isolate scope 對象,如今 HTML 模板被綁到了 directive.scope.user 上(經過 {{user.firstName}}{{user.lastName}} 內插指令)。

directive.scope.user 被設置爲 "=user" 。意思是說 directive.scope.userscope 中的屬性綁在一塊兒的(不是 isolate scope),scope 中的屬性經過 user 屬性傳入 <userinfo> 元素。聽起來挺費勁的,直接看例子:

<!-- lang: js -->
<userinfo user="jakob"></userinfo>
<userinfo user="john"></userinfo>

這兩個 <userinfo> 元素都有 user 屬性。user 的值指向了 $scope 中同名屬性,被指定的 $scope 中的屬性將在 userinfoisolate scope object 中被使用。

下面是完整的例子:

<!-- lang: js -->
<userinfo user="jakob"></userinfo>
<userinfo user="john"></userinfo>

<script>
myapp.directive('userinfo', function() {
    var directive = {};

    directive.restrict = 'E';

    directive.template = "User : <b>{{user.firstName}}</b> <b>{{user.lastName}}</b>";

    directive.scope = {
        user : "=user"
    }

    return directive;
});

myapp.controller("MyController", function($scope, $http) {
    $scope.jakob = {};
    $scope.jakob.firstName = "Jakob";
    $scope.jakob.lastName  = "Jenkov";

    $scope.john = {};
    $scope.john.firstName = "John";
    $scope.john.lastName  = "Doe";
});

</script>

#compile() 和 link() 函數

若是你須要在你的指令中作更高級的操做,單純使用 HTML 模板作不到的時候,你能夠考慮使用 compile()link() 函數。

compile()link() 函數定義了指令如何修改匹配到的 HTML。

當指令第一次被 AngularJS 編譯的時候 (在 HTML 中第一次被發現),compile() 函數被調用。compile() 函數將爲該元素作一次性配置。

而後 compile() 函數以返回 link() 做爲終結。link() 函數將在每次元素被綁到 $scope 數據時,都被調用。

下面是例子:

<!-- lang: js -->
<script>
myapp = angular.module("myapp", []);
myapp.directive('userinfo', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */


    directive.compile = function(element, attributes) {
        // do one-time configuration of element.

        var linkFunction = function($scope, element, atttributes) {
            // bind element to data in $scope
        }

        return linkFunction;
    }

    return directive;
});    
</script>

compile() 函數帶兩個參數: elementattributes

element 參數是 jqLite 包裝過的 DOM 元素。AngularJS 有一個簡化版 jQuery 能夠用於操做 DOM,因此對 element 的 DOM 操做方式和你在 jQuery 中所知的同樣。

attributes 參數是包含了 DOM 元素中的全部的屬性集合的 Javascript 對象。所以,你要訪問某個屬性你能夠這樣寫 attributes.type

link() 函數帶三個參數: $scopeelementattributeselementattributes 參數和傳到 compile() 函數中的同樣。$scope 參數是正常的 scope 對象,或者當你在指令定義對象中定義了 isolate scope 的時候,它就是 isolate scope。

compile()link() 的命名至關混亂。它們確定是受編譯隊伍的啓發而命名的。我能夠看到類似點,不過一個編譯器是傳入一次,而後輸出。而指令則是配置一次 HTML 元素,而後在以後的 $scope 對象改變時進行更新。

compile() 函數若是叫作 create(), init() 或者 configure()也許會更好。這樣能夠表達出這個函數只會被調用一次的意思。

link() 函數若是叫 bind() 或者 render() 也許會更好,能更好的表達出這樣的意思,在指令綁定數據或者重綁定數據的時候,這個函數將會被調用。

下面是一個完整的例子,演示了指令使用 compile()link() 函數的:

<!-- lang: js -->
<div ng-controller="MyController" >
    <userinfo >This will be replaced</userinfo>
</div>

<script>
    myapp = angular.module("myapp", []);
    myapp.directive('userinfo', function() {
        var directive = {};

        directive.restrict = 'E'; /* restrict this directive to elements */

        directive.compile = function(element, attributes) {
            element.css("border", "1px solid #cccccc");

            var linkFunction = function($scope, element, attributes) {
                element.html("This is the new content: " + $scope.firstName);
                element.css("background-color", "#ffff00");
            }

            return linkFunction;
        }

        return directive;
    })
    myapp.controller("MyController", function($scope, $http) {
        $scope.cssClass = "notificationDiv";

        $scope.firstName = "Jakob";

        $scope.doClick = function() {
            console.log("doClick() called");
        }
    });
</script>

compile() 函數設置 HTML 元素的 border 。它只執行一次,由於 compile() 函數只執行一次。

link() 替換 HTML 元素內容,而且把背景顏色設置爲黃色。

把設置 border 放在 compile(), 把背景顏色放在 link() 沒啥特別的理由。你能夠把全部的操做都丟到 compile(),或者都放到 link()。若是都放到 compile() 它們只會被設置一次(你須要它們是常量的話)。若是放到了 link(),它們會在每次 HTML 元素被綁到 $scope 的時候都發生變化。當你但願根據 $scope 中的數據來設置 boarder 和背景顏色的時候這很是有用。

##只設置 link() 函數

有時候你的指令可能不須要 compile() 。你只須要用到 link()。這種狀況下,你能夠直接設置指令定義對象中的 link() 函數。下面是一個對上面例子的修改,只用 link 函數:

<!-- lang: js -->
<div ng-controller="MyController" >
    <userinfo >This will be replaced</userinfo>
</div>

<script>
    myapp = angular.module("myapp", []);
    myapp.directive('userinfo', function() {
        var directive = {};

        directive.restrict = 'E'; /* restrict this directive to elements */

        directive.link = function($scope, element, attributes) {
                element.html("This is the new content: " + $scope.firstName);
                element.css("background-color", "#ffff00");
        }

        return directive;
    })
    myapp.controller("MyController", function($scope, $http) {
        $scope.cssClass = "notificationDiv";

        $scope.firstName = "Jakob";

        $scope.doClick = function() {
            console.log("doClick() called");
        }
    });
</script>

注意 link() 方法和以前例子中返回的 link() 是徹底同樣的。

#經過 Transclusion 封裝元素的指令

到目前爲止,咱們看到的全部例子,都是把匹配到的元素內容給替換爲指令的指定內容,不論是經過 Javascript 也好或者 HTML 模板也好。不過若是遇到內容中有部分是開發者能夠指定的內容的時候呢?好比說:

<!-- lang: js -->
<mytransclude>This is a transcluded directive {{firstName}}</mytransclude>

標記爲 <mytransclude> 的元素,它的部份內容能夠由開發者設置。所以,這部分 HTML 不該該被指令的 HTML 模板給替換。咱們其實是但願這部分 HTML 由 AngularJS 來處理的。這個處理叫作 "transclusion"。 1

爲了能讓 AngularJS 把這部分 HTML 放到指令內部處理,你必須設置指令定義對象的 transclude 屬性爲 true。你還須要告訴 AngularJS,指令的 HTML 模板中哪一部分須要把 transcluded HTML 包含進來。你能夠經過插入 ng-transclude 屬性 (實際上,是一個指令) 到 HTML 模板的 HTML 元素中,標記你想要添加 transcluded HTML 的元素。

下面是一個 AngularJS 指令,演示如何使用 transclusion:

<!-- lang: js -->
<mytransclude>This is a transcluded directive {{firstName}}</mytransclude>

<script>
    myapp = angular.module("myapp", []);
    myapp.directive('mytransclude', function() {
        var directive = {};

        directive.restrict = 'E'; /* restrict this directive to elements */
        directive.transclude = true;
        directive.template = "<div class='myTransclude' ng-transclude></div>";

        return directive;
    });
    myapp.controller("MyController", function($scope, $http) {
        $scope.firstName = "Jakob";
    });
</script>

注意 <mytransclude> 元素內的 HTML。這部分 HTML 代碼包含了內插指令 {{firstName}}。咱們但願 AngularJS 來爲咱們處理這部分 HTML,讓內插指令執行。爲了實現這個目的,我在指令定義對象中把 transclude 設置爲 true。我還在 HTML 模板中用到了 ng-transclude 屬性。這個屬性告訴 AngularJS 什麼元素須要插入到 transcluded HTML。


1: 說實話,我沒看懂那個定義,說的太TM難懂了,並且我好不爽寫代碼沒輸出的教程。只好本身動手作作例子。我以爲其實應該是這樣的,把目標元素內容做爲一個總體,拿到 HTML 模板中來,添加到 ng-transclude 指定的標籤下。這個處理,我以爲應該就是叫作 transcluded。好比說剛纔的例子(有些 bug,本身修正一下),由於 directive.transclude = true; ,因此原來 <mytransclude> 元素內的 HTML:

<!-- lang: js -->
This is a transcluded directive {{firstName}}

在激活指令 'mytransclude' 的時候,會被拿到 'mytransclude' 指令的模板中來,放到被 ng-transclude 指定的

<!-- lang: js -->
"<div class='myTransclude' ng-transclude></div>"

中。因而最終輸出的結果應該是:

<!-- lang: js -->
<mytransclude>
    <div class='myTransclude' ng-transclude>
        <span class="ng-scope ng-binding">This is a transcluded directive Jakob</span>
    </div>
</mytransclude>

而後我又回去把翻譯改了。如今感受念起來順口一點了。

相關文章
相關標籤/搜索