AngularJS 自定義控件

jopen 10年前發布 | 27K 次閱讀 JavaScript開發 angularjs

自定義指令介紹

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

當然你也可以實現自己的。這就是 AngularJS 所謂的"教會 HTML 玩新姿勢”。本文將告訴你如何做到。

指令類型

可以自定義的指令類型如下:

  • 元素
  • 屬性
  • CSS class
  • Comment directives
  • </ul>

    這里面,AngularJS 強烈建議你用元素和屬性類型,而不用 CSS class 和 comment directives (除非迫不得已)。

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

    基礎例子

    你可以向模塊注冊一個指令,像這樣:

    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;
    

    });</pre>

    來看模塊中調用的 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:

    <div ng-controller="MyController" >
        <div>This div will be replaced</div>
    </div>

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

    My first directive: {{textToInsert}}

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

    template 和 templateUrl 屬性

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

    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;
    

    });</pre>

    如果 HTML 模板越來越大,把它寫在一個 Javascript 字符串中肯定非常難維護。你可以把它放到一個單獨的文件中,然后 AngularJS 可以通過這個文件把它加載進來。然后再把 HTML 模板文件的 URL 放到指令定義對象的 templateUrl 屬性中。下面是例子:

    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;
    

    });</pre>

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

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

    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;
    

    });</pre>

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

    從指令中隔離 $scope

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

    <userinfo></userinfo>
    <userinfo></userinfo>

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

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

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

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

    directive.restrict = 'E';
    
    directive.template = "User : {{user.firstName}} {{user.lastName}}";
    
    directive.scope = {
        user : "=user"
    }
    
    return directive;
    

    })</pre>

    請看 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> 元素。聽起來挺費勁的,直接看例子:

    <userinfo user="jakob"></userinfo>
    <userinfo user="john"></userinfo>

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

    下面是完整的例子:

    <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></pre>

    compile() 和 link() 函數

    如果你需要在你的指令中做更高級的操作,單純使用 HTML 模板做不到的時候,你可以考慮使用 compile()link() 函數。

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

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

    然后 compile() 函數以返回 link() 作為終結。link() 函數將在每次元素被綁到 $scope 數據時,都被調用。

    下面是例子:

    <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></pre>

    ` <br />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() 函數的:

    <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></pre>

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

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

    把設置 border 放在 compile(), 把背景顏色放在 link() 沒啥特別的理由。你可以把所有的操作都丟到 compile(),或者都放到 link()。如果都放到 compile() 它們只會被設置一次(你需要它們是常量的話)。如果放到了 link(),它們會在每次 HTML 元素被綁到 $scope 的時候都發生變化。當你希望根據 $scope 中的數據來設置 boarder 和背景顏色的時候這非常有用。

    只設置 link() 函數

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

    <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></pre>

    注意 link() 方法和之前例子中返回的 link() 是完全一樣的。

    通過 Transclusion 封裝元素的指令

    到目前為止,我們看到的所有例子,都是把匹配到的元素內容給替換為指令的指定內容,不管是通過 Javascript 也好或者 HTML 模板也好。不過如果遇到內容中有部分是開發者可以指定的內容的時候呢?比如說:

    <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:

    <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></pre>

    注意 <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:

    This is a transcluded directive {{firstName}}

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

    "<div class='myTransclude' ng-transclude></div>"

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

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

    來自:http://my.oschina.net/ilivebox/blog/289670

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!