Angular Directive 詳解

Angular Directive 學習

學習目的:爲了更好的瞭解 ng directive 的使用方法。

Directive多是AngularJS中比較複雜的一個東西了。通常咱們將其理解成指令。AngularJS自帶了很多預設的指令,好比ng-app,ng-controller這些。能夠發現個特色,AngularJS自帶的指令都是由ng-打頭的。javascript

那麼,Directive到底是個怎麼樣的一個東西呢?我我的的理解是這樣的:將一段html、js封裝在一塊兒,造成一個可複用的獨立個體,具體特定的功能。下面咱們來詳細解讀一下Directive的通常性用法。html

AnguarJS directive的經常使用定義格式以及參數說明

看下面的代碼:

var myDirective = angular.module('directives', []);

myDirective.directive('directiveName', function($inject) {
    return {
        template: '<div></div>',
        replace: false,
        transclude: true,
        restrict: 'E',
        scope: {},
        controller: function($scope, $element) {

        },
        complie: function(tElement, tAttrs, transclude) {
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {

                },
                post: function postLink(scope, iElement, iAttrs, controller) {

                }
            };
        },
        link: function(scope, iElement, iAttrs) {

        }
    };
});
複製代碼
  • 這裏直接return了一個object,對象中包括比較多的屬性,這些屬性都是對自定義directive的定義。詳細的含義,下面會繼續說明。
  • return對象參數說明
return {
    name: '',
    priority: 0,
    terminal: true,
    scope: {},
    controller: fn,
    require: fn,
    restrict: '',
    template: '',
    templateUrl: '',
    replace: '',
    transclude: true,
    compile: fn,
    link: fn
}
複製代碼

如上所示,return的對象中會有不少的屬性,這行屬性都是用來定義directive的。java

下面咱們來一個個的說明他們的做用。

  • namejquery

    • 表示當前scope的名稱,通常聲明時使用默認值,不用手動設置此屬性。
  • priorityangularjs

    • 優先級。當有多個directive定義在同一個DOM元素上時,有時須要明確他們的執行順序。這個屬性用於在directive的compile function調用以前進行排序。若是優先級相同,則執行順序是不肯定的(根據經驗,優先級高的先執行,相同優先級時按照先綁定後執行)。
  • teminalexpress

    • 最後一組。若是設置爲true,則表示當前的priority將會成爲最後一組執行的directive,即比此directive的priority更低的directive將不會執行。同優先級依然會執行,可是順序不肯定。
  • scopesegmentfault

    • true
      • 將爲這個directive建立一個新的scope。若是在同一個元素中有多個directive須要新的scope的話,它仍是隻會建立一個scope。新的做用域規則不適用於根模版,由於根模版每每會得到一個新的scope。
    • {}
      • 將建立一個新的、獨立的scope,此scope與通常的scope的區別在於它不是經過原型繼承於父scope的。這對於建立可複用的組件是頗有幫助的,能夠有效的防止讀取或者修改父級scope的數據。這個獨立的scope會建立一個擁有一組來源於父scope的本地scope屬性hash集合。這些本地scope屬性對於模版建立值的別名頗有幫助。本地的定義是對其來源的一組本地scope property的hash映射。
  • controllerbash

    • controller構造函數。controller會在pre-linking步驟以前進行初始化,並容許其餘directive經過指定名稱的require進行共享。這將容許directive之間相互溝通,加強相互之間的行爲。controller默認注入瞭如下本地對象:
      • $scope 與當前元素結合的scope
      • $element 當前的元素
      • $attrs 當前元素的屬性對象
      • $transclude 一個預先綁定到當前scope的轉置linking function
  • requireapp

    • 請求另外的controller,傳入當前directive的linking function中。require須要傳入一個directive controller的名稱。若是找不到這個名稱對應的controller,那麼將會拋出一個error。名稱能夠加入如下前綴:
      • ? 不要拋出異常。這將使得這個依賴變爲一個可選項
      • ^ 容許查找父元素的controller
  • restrict異步

    • EACM的子集的字符串,它限制了directive爲指定的聲明方式。若是省略的話,directive將僅僅容許經過屬性聲明
      • E 元素名稱:
      • A 屬性名:
      • C class名:
      • M 註釋:
  • template

    • 若是replace爲true,則將模版內容替換當前的html元素,並將原來元素的屬性、class一併轉移;若是replace爲false,則將模版元素看成當前元素的子元素處理。
  • templateUrl

    • 與template基本一致,但模版經過指定的url進行加載。由於模版加載是異步的,全部compilation、linking都會暫停,等待加載完畢後再執行。
  • replace

    • 若是設置爲true,那麼模版將會替換當前元素,而不是做爲子元素添加到當前元素中。(爲true時,模版必須有一個根節點)
  • transclude

    • 編譯元素的內容,使它可以被directive使用。須要在模版中配合ngTransclude使用。transclusion的有點是linking function可以獲得一個預先與當前scope綁定的transclusion function。通常地,創建一個widget,建立獨立scope,transclusion不是子級的,而是獨立scope的兄弟級。這將使得widget擁有私有的狀態,transclusion會被綁定到父級scope中。(上面那段話沒看懂。但實際實驗中,若是經過調用myDirective,而transclude設置爲true或者字符串且template中包含的時候,將會將的編譯結果插入到sometag的內容中。若是any的內容沒有被標籤包裹,那麼結果sometag中將會多了一個span。若是原本有其餘東西包裹的話,將維持原狀。但若是transclude設置爲’element’的話,any的總體內容會出如今sometag中,且被p包裹)
      • true/false 轉換這個directive的內容。(這個感受上,是直接將內容編譯後搬入指定地方)
      • ‘element’ 轉換整個元素,包括其餘優先級較低的directive。(像將總體內容編譯後,看成一個總體(外面再包裹p),插入到指定地方)
  • compile

    • 這裏是compile function,將在下面實例詳細說明
  • link

    • 這裏是link function ,將在下面實例詳細講解。這個屬性僅僅是在compile屬性沒有定義的狀況下使用。

關於scope

這裏關於directive的scope爲一個object時,有更多的內容很是有必要說明一下。看下面的代碼:

scope: {
    name: '=',
    age: '=',
    sex: '@',
    say: '&'
}
複製代碼

這個scope中關於各類屬性的配置出現了一些奇怪的前綴符號,有=,@,&,那麼這些符號具體的含義是什麼呢?再看下面的代碼:

  • html
<div my-directive name="myName" age="myAge" sex="male" say="say()"></div>
複製代碼
  • javascript
function Controller($scope) {
    $scope.name = 'Pajjket';
    $scope.age = 99;
    $scope.sex = '我是男的';
    $scope.say = function() {
        alert('Hello,我是彈出消息');
    };
}
複製代碼
能夠看出,幾種修飾前綴符的大概含義:
  • =: 指令中的屬性取值爲Controller中對應$scope上屬性的取值
  • @: 指令中的取值爲html中的字面量/直接量
  • &: 指令中的取值爲Controller中對應$scope上的屬性,可是這個屬性必須爲一個函數回調 下面是更加官方的解釋:
  • =或者=expression/attr

在本地scope屬性與parent scope屬性之間設置雙向的綁定。若是沒有指定attr名稱,那麼本地名稱將與屬性名稱一致。

  • 例如: 中,widget定義的scope爲:{localModel: '=myAttr'},那麼widget scope property中的localName將會映射父scope的parentModel。若是parentModel發生任何改變,localModel也會發生改變,反之亦然。即雙向綁定。

  • @或者@attr 創建一個local scope property到DOM屬性的綁定。由於屬性值老是String類型,因此這個值總返回一個字符串。若是沒有經過@attr指定屬性名稱,那麼本地名稱將與DOM屬性的名稱一致。 例如: ,widget的scope定義爲:{localName: '@myAttr'}。那麼,widget scope property的localName會映射出"hello "轉換後的真實值。當name屬性值發生改變後,widget scope的localName屬性也會相應的改變(僅僅是單向,與上面的=不一樣)。那麼屬性是在父scope讀取的(不是從組件的scope讀取的)

  • &或者&attr 提供一個在父scope上下文中執行一個表達式的途徑。若是沒有指定attr的名稱,那麼local name將與屬性名一致。

    • 例如:

<widget my-attr="count = count + value">,widget的scope定義爲:{localFn:’increment()’},那麼isolate scope property localFn會指向一個包裹着increment()表達式的function。 通常來講,咱們但願經過一個表達式,將數據從isolate scope傳到parent scope中。這能夠經過傳送一個本地變量鍵值的映射到表達式的wrapper函數中來完成。例如,若是表達式是increment(amount),那麼咱們能夠經過localFn({amount:22})的方式調用localFn以指定amount的值。

directive 實例講解

下面的示例都圍繞着上面所做的參數說明而展開的。

  • directive聲明實例
// 自定義directive
var myDirective = angular.modeule('directives', []);

myDirective.directive('myTest', function() {
    return {
        restrict: 'EMAC',
        require: '^ngModel',
        scope: {
            ngModel: '='
        },
        template: '<div><h4>Weather for {{ngModel}}</h4</div>'
    };
});

// 定義controller
var myControllers = angular.module('controllers', []);
myControllers.controller('testController', [
    '$scope',
    function($scope) {
        $scope.name = 'this is directive1';
    }
]);


var app = angular.module('testApp', [
    'directives',
    'controllers'
]);

<body ng-app="testApp">
    <div ng-controller="testController">
        <input type="text" ng-model="city" placeholder="Enter a city" />
        <my-test ng-model="city" ></my-test>
        <span my-test="exp" ng-model="city"></span>
        <span ng-model="city"></span>
    </div>
</body>
複製代碼

template與templateUrl的區別和聯繫

templateUrl其實根template功能是同樣的,只不過templateUrl加載一個html文件,上例中,咱們也能發現問題,template後面根的是html的標籤,若是標籤不少呢,那就比較不爽了。能夠將上例中的,template改一下。

myDirective.directive('myTest', function() {
    return {
        restrict: 'EMAC',
        require: '^ngModel',
        scope: {
            ngModel: '='
        },
        templateUrl:'../partials/tem1.html'   //tem1.html中的內容就是上例中template的內容。
    }
});
複製代碼

scope重定義

//directives.js中定義myAttr
myDirectives.directive('myAttr', function() {
    return {
        restrict: 'E',
        scope: {
            customerInfo: '=info'
        },
        template: 'Name: {{customerInfo.name}} Address: {{customerInfo.address}}<br>' +
                  'Name: {{vojta.name}} Address: {{vojta.address}}'
    };
});

//controller.js中定義attrtest
myControllers.controller('attrtest',['$scope',
    function($scope) {
        $scope.naomi = {
            name: 'Naomi',
            address: '1600 Amphitheatre'
        };
        $scope.vojta = {
            name: 'Vojta',
            address: '3456 Somewhere Else'
        };
    }
]);

// html中
<body ng-app="testApp">
    <div ng-controller="attrtest"> <my-attr info="naomi"></my-attr> </div> </body>
複製代碼

其運行結果以下:

Name: Naomi Address: 1600 Amphitheatre      //有值,由於customerInfo定義過的
Name: Address:                              //沒值 ,由於scope重定義後,vojta是沒有定義的
複製代碼

咱們將上面的directive簡單的改一下,

myDirectives.directive('myAttr', function() {
    return {
        restrict: 'E',
        template: 'Name: {{customerInfo.name}} Address: {{customerInfo.address}}<br>' +
                  'Name: {{vojta.name}} Address: {{vojta.address}}'
    };
});
複製代碼
  • 運行結果以下:
Name: Address:
Name: Vojta Address: 3456 Somewhere Else
複製代碼

由於此時的directive沒有定義獨立的scope,customerInfo是undefined,因此結果正好與上面相反。

transclude的使用

  • transclude的用法,有點像jquery裏面的$().html()功能
myDirective.directive('myEvent', function() {
    return {
        restrict: 'E',
        transclude: true,
        scope: {
            'close': '$onClick'      //根html中的on-click="hideDialog()"有關聯關係
        },
        templateUrl: '../partials/event_part.html'
    };
});

myController.controller('eventTest', [
    '$scope',
    '$timeout',
    function($scope, $timeout) {
        $scope.name = 'Tobias';
        $scope.hideDialog = function() {
            $scope.dialogIsHidden = true;
            $timeout(function() {
                $scope.dialogIsHidden = false;
            }, 2000);
        };
    }
]);
複製代碼
<body ng-app="phonecatApp">
    <div ng-controller="eventtest">
        <my-event ng-hide="dialogIsHidden" on-click="hideDialog()">
            Check out the contents, {{name}}!
        </my-event>
    </div>
</body>

<!--event_part.html -->
<div>
    <a href ng-click="close()">×</a>
    <div ng-transclude></div>
</div>
複製代碼
  • 說明:這段html最終的結構應該以下所示:
<body ng-app="phonecatApp">
    <div ng-controller="eventtest">
        <div ng-hide="dialogIsHidden" on-click="hideDialog()">
            <span>Check out the contents, {{name}}!</span>
        </div>
    </div>
</body>
複製代碼
  • 將原來的html元素中的元素Check out the contents, !插入到模版的
    中,還會另外附加一個標籤。 controllerlinkcompile之間的關係
myDirective.directive('exampleDirective', function() {
    return {
        restrict: 'E',
        template: '<p>Hello {{number}}!</p>',
        controller: function($scope, $element){
            $scope.number = $scope.number + "22222 ";
        },
        link: function(scope, el, attr) {
            scope.number = scope.number + "33333 ";
        },
        compile: function(element, attributes) {
            return {
                pre: function preLink(scope, element, attributes) {
                    scope.number = scope.number + "44444 ";
                },
                post: function postLink(scope, element, attributes) {
                    scope.number = scope.number + "55555 ";
                }
            };
        }
    }
});

//controller.js添加
myController.controller('directive2',[
    '$scope',
    function($scope) {
        $scope.number = '1111 ';
    }
]);

//html
<body ng-app="testApp">
    <div ng-controller="directive2"> <example-directive></example-directive> </div> </body>
複製代碼
  • 上面小例子的運行結果以下:
Hello 1111 22222 44444 5555 !
複製代碼

由結果能夠看出來,controller先運行,compile後運行,link不運行。 咱們如今將compile屬性註釋掉後,獲得的運行結果以下: Hello 1111 22222 33333 !

由結果能夠看出來,controller先運行,link後運行,link和compile不兼容。通常地,compile比link的優先級要高。

參考

相關文章
相關標籤/搜索