AngularJS 指令入門

指令是運行在特定 dom 元素上的函數,用來擴展元素的功能。javascript

一個簡版的 directive 的形式是這樣的html

app.directive('myDirective', myDirective);

myDirective.$inject = [];

function myDirective(){
    return {
        restrict: "AE",
        template: ''
        scope: {},
        link: function(){}
    }
}

下面介紹一些經常使用的選項。java

選項

restrict(String)

表示指令在 dom 中以何種形式被聲明angularjs

  • E 元素segmentfault

  • A 屬性(默認值)數組

  • C 類名緩存

  • M 註釋(不建議使用)app

屬性(E)是最經常使用的聲明指令方式,由於能兼容老版本IE,且不引入新標籤。元素(A)方式適合建立一個徹底獨立、完整的組件。dom

priority(Number)

指令優先級,默認爲 0(最高1000,好比 ng-repeat ),若是須要同一元素上一個指令比另外一個指令先被調用,可給它設置更高的優先級。異步

在 Angular 1.2 裏面,若是使用 checkbox,同時綁定 ng-modelng-click,而後根據 ng-model 的值在 ng-click 裏進行相應的處理,像下面這樣:

<input type="checkbox" ng-model="formData.status" ng-click="check()">
$scope.check = function(){
    if($scope.formData.status){
        alert('true');
    }else{
        alert('false');
    }
}

會發現,ng-model 的值和 check 裏面的邏輯是相反的。其實這是由於在 Angular 1.2 裏面,ng-click 的優先級比 ng-model 高,致使在值更新前,ng-click 就被觸發。修復辦法是用 ng-change 替換 ng-click 。在 Angular 1.3 以上版本已經修復了這個問題。

template 和 templateUrl(String || Function)

HTML 模版文件。

在調用指令後,模版文件是經過 XHR 來加載的,加載後會緩存到 $templateCache 服務中。

經過 AJAX 異步加載大量的模版會拖慢一個 Angular 應用的速度。能夠提早將模版緩存到一個定義模版的 js 文件中,或者在加載 ng-app 的首頁同時將模版以 script 標籤加載,而後在配置 template 的地方經過 js 獲取。

<script id="pagerTpl" type="text/html">
    <div>...</div>
</script>

template: document.querySelector('#pagerTpl').innerHTML

transclude(Boolean)

當指令定義的標籤內部也有標籤的時候,transclude 設置爲 true 可讓子標籤的內容保持不變。

transclude 一般用來建立可複用的組件,能夠將整個模版,包括其中的指令經過 transclude 所有傳入一個指令中。典型的應用是模態對話框或導航欄。

replace(Boolean)

replace 默認爲 false,表示模版會被看成子元素插入到調用此指令的元素內部。

設置爲 true,則指令會被模版替換掉。

scope(Boolean || Object)

scope 默認爲 false,表示指令內和指令外共用同一做用域。設置爲 true 的時候,表示從父做用域繼承並建立一個新的做用域對象(是否是感受很像 ng-show 和 ng-if 的區別),不過這兩種狀況用的比較少(做用域污染)。

最經常使用的經過一個空對象 {},產生一個隔離做用域,用於建立可複用的組件。組件能夠在未知上下文中使用,而且組件所處的外部做用域和內部做用域不會被不經意地污染。

但大多時候並不會直接使用無數據的隔離做用域。有三種辦法讓內部的隔離做用域同外部的做用域進行數據綁定。

=

= 是最經常使用的方式,經過它能夠將本地做用域的屬性同父級做用域上的屬性進行雙向綁定。

在元素中使用屬性的方式爲(不可使用 {% raw %}{{}}{% endraw %}):

<div my-directive age="age"></div>

scope: {
    age: '='
}

@

使用 @ 能夠將本地做用域同 dom 屬性的值進行單向綁定,在使用父做用域中的值對指令中的屬性初始賦值後,指令中屬性值的修改不會影響父做用域中的值。

在元素中的使用方式爲(有 {% raw %}{{}}{% endraw %}):

<div my-directive my-name="{{name}}"></div>

// 視圖中的 - 鏈接的屬性轉換到指令定義中要使用駝峯式
scope: {
    name: '@myName'
}

&

這是一個綁定函數方法的前綴標識符,經過 & 能夠對父級做用域進行綁定,以便在其中運行函數。

在元素中的使用方式爲:

<div my-directive change="changeName()"></div>

scope: {
    changeName: '&change'
}

更詳細的栗子參見 這篇文章 的講解。

controller(String || Function)

通常狀況下不須要使用指令的 controller,只要使用 link 就夠了,controllerlink 函數能夠互換。但它是在預編譯階段執行的(先於 compile)。

controller 的場景是該指令(a)會被其餘指令(b)require 的時候,在 b 的指令裏能夠傳入 a 的這個 controller,目的是爲了指令間的複用和交流。而 link 只能在指令內部中定義行爲,沒法作到這樣。

controller 的形式是這樣的:

controller: function($scope, $ele, $attrs, $transclude){
    ...
}

require(String || Array)

通常狀況下不須要使用 require,它是和 controller 結合用的。require 參數用來引入其餘指令或一個指令數組,並將引入指令的 controller 傳給 link 函數的第四個參數。

編譯 和 連接

AngularJS 應用啓動後,會經歷兩個階段。一是編譯階段(compile),對模版 dom 進行轉換(指令標籤解析和變換),二是連接階段(link),將做用域和 dom 進行連接(數據綁定)。

編譯

在編譯階段,AngularJS 會遍歷整個 HTML 文檔並根據 JavaScript 中的指令定義來處理頁面上聲明的指令。

編譯階段能夠改變原始的 domtemplate element),因爲此階段還未對 dom 樹進行數據綁定,因此此時對 dom 樹操做只須要較少的性能開銷。好比 ng-repeat(改變原始 dom 生成多個 dom 節點) 和 ng-transclude(嵌入模版到指令中) 都是在這個階段對 dom 進行操做。

compile 後,返回一個函數(link)或對象(preLinkpostLink)。

compile 函數形式:

/**
* Compile function
* 
* @param tElem - template element
* @param tAttrs - attributes of the template element
*/
function(tEle, tAttrs){
    ...
}

連接

連接階段完成做用域和 dom 之間數據的綁定,dom 事件監聽器的註冊,也是在這個階段作。

link 函數形式:

/**
* pre-link and post-link function
* 
* @param scope - scope associated with this instance
* @param iElem - instance element
* @param iAttrs - attributes of the instance element
*/
function(scope, iElem, iAttrs, ctrl){
    ...
}

若是指令定義中有 require 選項,link 的函數簽名中還會有第四個參數,表明控制器或所依賴指令的控制器。

二者區別

  • compile 是對指令的模板進行轉換,link 是在模型和視圖之間創建關聯(包括註冊事件監聽)

  • compile 對同一個指令的多個實例只會執行一次,link 對於指令的每一個實例都會執行一次

  • compile 和 link 是互斥的,編寫了 compile,自定義的 link 將無效

  • 通常狀況下只須要編寫 link

栗子

舉個簡單的行內編輯的栗子,默認顯示,雙擊可編輯。

directive 文件

app.directive('inlineEdit', inlineEdit);

inlineEdit.$inject = ['$document'];

function inlineEdit($document){
    return {
        restrict: 'A',
        templateUrl: 'inline-edit.html',
        scope: {
            desc: '=ngModel'
        },
        link: function(scope, ele, attrs){
            scope.isEdit = false;
            ele.on('dblclick', function(){
                scope.isEdit = true;
                scope.$digest();
            })
            $document.on('click', function(evt){                
                var src = evt.srcElement || evt.target,
                    parent = src.parentNode;
                if(parent.classList && parent.classList[0] == 'inline-container'){
                    return;
                }
                scope.isEdit = false;
                scope.$digest();
            })
        }
    }
}

template 文件

<div class="inline-container" ng-show="isEdit">
    <input type="text" ng-model="desc">
</div>
<div ng-if="!isEdit">
    {{desc}}
</div>

而後,能夠這樣使用

<div ng-model="desc" inline-edit></div>
相關文章
相關標籤/搜索