AngularJS - 自定義指令

這一篇從自定義指令出發,記錄了定義一個指令時影響指令行爲的各類因素。javascript

試着感覺這些因素,讓本身更高效地編寫AngularJS應用。html

Directive

先從定義一個簡單的指令開始。
定義一個指令本質上是在HTML中經過元素、屬性、類或註釋來添加功能。java

AngularJS的內置指令都是以ng開頭,若是想自定義指令,建議自定義一個前綴表明本身的命名空間。express

這裏咱們先使用my做爲前綴:app

var myApp = angular.module('myApp', [])
    .directive('myDirective', function() {
    return {
        restrict: 'A',
        replace: true,
        template: '<p>Kavlez</p>'
    };
})

如此一來,咱們能夠這樣使用,注意命名是camel-case:函數

<my-directive />
<!-- <my-directive><p>Kavlez</p></my-directive> -->

directive()接受兩個參數spa

  • name:字符串,指令的名字
  • factory_function:函數,指令的行爲

應用啓動時,以name做爲該應用的標識註冊factory_function返回的對象。翻譯

在factory_function中,咱們能夠設置一些選項來改變指令的行爲。雙向綁定

下面記錄一下定義指令時用到的選項rest

restrict (string)

該屬性用於定義指令以什麼形式被使用,這是一個可選參數,本文開頭定義的指令用的也是A,其實該選項默認爲A。

也就是元素(E)、屬性(A)、類(C)、註釋(M)

(ps:EMAC? EMACS? 挺好記哈)

好比上面定義的myDirective,能夠以任何形式調用。

  • E(元素)
<my-directive></my-directive>
  • A(屬性,默認值)
<div my-directive="expression"></div>
  • C(類名)
<div class="my-directive:expression;"></div>
  • M(註釋)
<--directive:my-directive expression-->

priority (Number)

也就是優先級,默認爲0。

在同一元素上聲明瞭多個指令時,根據優先級決定哪一個先被調用。

若是priority相同,則按聲明順序調用。

另外,no-repeat是全部內置指令中優先級最高的。

terminal (Boolean)

終端? 並且仍是Boolean?

被名字嚇到了,其實terminal的意思是是否中止當前元素上比該指令優先級低的指令
可是相同的優先級仍是會執行。

好比,咱們在my-directive的基礎上再加一個指令:

.directive('momDirective',function($rootScope){
    return{
        priority:3,
        terminal:true
    };
})

調用發現my-directive不會生效:

<div mom-directive my-directive="content" ></div>

 

template (String/Function)

至少得輸出點什麼吧? 但template也是可選的。

String類型時,template能夠是一段HTML。

Function類型時,template是一個接受兩個參數的函數,分別爲:

  • tElement
  • tAttrs

函數返回一段字符串做爲模板。

templateUrl (String/Function)

這個就和上面的template很像了,只不過此次是經過URL請求一個模板。
String類型時,templateURL天然是一個URL。
Function類型時返回一段字符串做爲模板URL。

replace (Boolean/String)

默認值爲false,以文章開頭定義的指令爲例,假設咱們這樣調用了指令

<my-directive></my-directive>

replace爲true時,輸出:

<p>Kavlez</p>

replace爲false時,輸出:

<my-directive><p>Kavlez</p></my-directive>

 

transclude (Boolean)

該選項默認爲false,翻譯過來叫'嵌入',感受仍是有些生澀。

templatescope已經能夠作不少事情了,但有一點不足。

好比在原有元素的基礎上添加內容,transclude的例子以下:

<body ng-app="myApp">
    <textarea ng-model="content"></textarea>
    <div my-directive title="Kavlez">
        <hr>
        {{content}}
    </div>
</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.directive('myDirective', function() {
    return {
        restrict: 'EA',
        scope: {
            title: '@',
            content: '='
        },
        transclude: true,
        template: '<h2 class="header">{{ title }}</h2>\
        <span class="content" ng-transclude></span>'
    };
});
</script>

發現div下的hr並無被移除,就是這樣的效果。

注意不要忘了在模板中聲明ng-transclude

scope (Boolean/Object)

默認爲false,true時會從父做用域繼承並建立一個本身的做用域。

ng-controller的做用也是從父做用域繼承並建立一個新的做用域。

好比這樣,離開了本身的做用域就被打回原形了:

<div ng-init="content='from root'">
    {{content}}
    <div ng-controller="AncestorController">
        {{content}}     
        <div ng-controller="ChildController">
            {{content}}     
        </div>
        {{content}} 
    </div>
    {{content}} 
</div>

.controller('ChildController', function($scope) {
    $scope.content = 'from child';
})
.controller('AncestorController', function($scope) {
    $scope.content = 'from ancestor';
})

但不要誤解,指令嵌套並不必定會改變它的做用域。

既然true時會從父做用域繼承並建立一個本身的做用域,那麼咱們來試試改成false會是什麼樣子:

<div ng-init="myProperty='test'">
    {{ myProperty }}
    <div my-directive ng-init="myProperty = 'by my-directive'">
        {{ myProperty }}
    </div>
    {{ myProperty }}
</div>

.directive('myDirective', function($rootScope) {
    return {
        scope:false
    };
})

顯然,結果是三行'by my-directive'。

非true即false? naive!

其實最麻煩的仍是隔離做用域

咱們稍微改動一下myDirective,改成輸出<p>{{內容}}</p>

因而我試着這樣定義:

<body ng-app="myApp" >
    <p ng-controller="myController">
    <div my-directive="I have to leave." ></div>
        {{myDirective}}
    </p>
</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.directive('myDirective', function($rootScope) {
    $rootScope.myDirective = 'from rootScope';
    return {
        priority:1000,
        restrict: 'A',
        replace: true,
        scope: {
            myDirective: '@',
        },
        template: '<p>{{myDirective}}</p>'
    };
})
.controller('myController',function($scope){
    $scope.myDirective = 'from controller';
});
</script>

這裏須要注意的不是@,重點是隔離做用域

根據上面的例子輸出,template中的{{myDirective}}不會影響到其餘做用域。

咱們再試試這樣:

<input type="text" ng-model="content">
<p ng-controller="myController" >
<div my-directive="{{content}}" ></div>
    {{content}}
</p>

發現你們都在一塊兒變,也就是說值是經過複製DOM屬性並傳遞到隔離做用域。

ng-model是個強大的指令,它將本身的隔離做用域和DOM做用域連在一塊兒,這樣就是一個雙向數據綁定。

如何向指令的隔離做用域中傳遞數據,這裏用了@

或者也能夠寫成@myDirective,也就是說換個名字什麼的也能夠,好比我用@myCafe什麼的給myDirective賦值也是沒問題的,總之是和DOM屬性進行綁定。

另外,咱們也能夠用=進行雙向綁定,將本地做用域的屬性同父級做用域的屬性進行雙向綁定

好比下面的例子中,隔離做用域裏的內容只能是'abc' :

<body ng-app="myApp" ng-init="content='abc'">
    <p ng-controller="myController" >
        <input type="text" ng-model="content">
        <div my-directive="content" ></div>
        {{content}}
    </p>
</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.directive('myDirective', function($rootScope) {
    return {
        priority:1000,
        restrict: 'A',
        replace: true,
        scope: {
            myDirective: '=',
        },
        template: '<p>from myDirective:{{myDirective}}</p>'
    };
})  
.controller('myController',function($scope){
    $scope.content = 'from controller';
});
</script>

在隔離做用域訪問指令外部的做用域的方法還有一種,就是&

咱們可使用&與父級做用域的函數進行綁定,好比下面的例子:

<body ng-app="myApp">
    <div ng-controller="myController">
        <table border='1'>
            <tr>
                <td>From</td>
                <td><input type="text" ng-model="from"/></td>
            </tr>
            <tr>
                <td>To</td>
                <td><input type="text" ng-model="to"/></td>
            </tr>
            <tr>
                <td>Content</td>
                <td><textarea cols="30" rows="10" ng-model="content"></textarea></td>
            </tr>
            <tr>
                <td>Preview:</td>
                <td><div scope-example to="to" on-send="sendMail(content)" from="from" /></td>
            </tr>
        </table>
    </div>
</div>

</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.controller('myController',function($scope){
    $scope.sendMail=function(content){
        console.log('content is:::'+content);
    }
})
.directive('scopeExample',function(){
    return{
        restrict:'EA',
        scope: {
            to: '=', 
            from: '=' ,
            send: '&onSend'
        },
        template:'<div>From:{{from}}<br>\
        To:{{to}}<br>\
        <button ng-click="send()">Send</button>\
        </div>'
    }
})
</script>

controller (String/Function)

控制器也能夠在指令裏定義,好比:

.directive('myDirective', function() {
    restrict: 'A',
    controller: 'myController'
}).controller('myController', function($scope, $element, $attrs,$transclude) {
    //...
})

相同的效果,也能夠這樣聲明:

directive('myDirective', function() {
    restrict: 'A',
    controller:function($scope, $element, $attrs, $transclude) {
        //...
    }
});  

controllerAs (String)

能夠從名字和類型看出,這個選項是用來設置控制器的別名的。

好比這樣:

directive('myDirective', function() {
    restrict: 'A',
    controller:function($scope, $element, $attrs, $transclude) {
        //...
    }
});

compile (Object/Function)

雖然說這個東西不是很經常使用吧,但倒是值得了解的選項。

compilelink,這兩個選項關係到AngularJS的生命週期。

先在這裏簡單記錄一下我對生命週期的認識。

  • 應用啓動前,全部的指令以文本的形式存在。* 應用啓動後便開始進行compilelink,DOM開始變化,做用域與HTML進行綁定。* 在編譯階段,AngularJS會遍歷整個HTML並處理已聲明的指令。
  • 一個指令的模板中可能使用了另一個指令,這個指令的模板中可能包含其餘指令,如此層層下來即是一個模板樹。* 在DOM還沒有進行數據綁定時對DOM進行操做開銷相對較小,這時像ng-repeat之類的指令對DOM進行操做則再合適不過了。
  • 咱們能夠用編譯函數訪問編譯後的DOM,在數據綁定以前用編譯函數對模板DOM進行轉換,編譯函數會返回模板函數。
    也就是說,設置compile函數的意義在於:在指令和實時數據被放到DOM中以前修改DOM
    此時徹底能夠毫無顧慮地操做DOM。
  • 接着咱們即可以進入下一個階段,連接階段
  • 最後,模板函數傳遞給指令指定的連接函數,連接函數對做用域和DOM進行連接。

好了,接下來咱們就試試compile:

<body ng-app="myApp">
    <my-directive ng-model="myName"></my-directive>
</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.directive('myDirective', function($rootScope) {
    $rootScope.myName = 'Kavlez';
    return {
        restrict: 'EA',
        compile:function(tEle, tAttrs, transcludeFn) {
            var h2 = angular.element('<h2></h2>');
            h2.attr('type', tAttrs.type);
            h2.attr('ng-model', tAttrs.ngModel);
            h2.html("hello {{"+tAttrs.ngModel+"}}");
            tEle.replaceWith(h2);
        }
    };
});
</script>

原文出處 AngularJS - 自定義指令

相關文章
相關標籤/搜索