Angular自定義指令Directive詳解

「控制器應該儘量保持短小精悍,而在控制器中進行DOM操做和數據操做則是一個很差的實踐。設計良好的應用會將複雜的邏輯放到指令和服務中。經過使用指令和服務,咱們能夠將控制器重構成一個輕量且更易維護的形式」 ----《AngularJs權威教程》

一、Angular Directive說明

Directive中文翻譯爲指令,從《AngularJs權威教程》中對指令的描述,我理解的指令是對控制器的補充,主要功能是對Dom元素和數據的操做(通常指令主Dom操做、服務主數據操做,非強制規定),本文基於Angular1.4.6版本進行講解和演示。css

在Angular中內置了一些指令,如常見的ng-app指令初始化一個 AngularJS 應用程序,ng-model指令把元素值(好比輸入域的值)綁定到應用程序。同時Angular也支持建立自定義指令。html

二、Angular Directive使用

基礎案例展現。網絡

Html代碼:app

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <meta charset="utf-8">
    <script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body ng-controller="myCtrl">
    <!-- 引入指令 -->
    <my-dir></my-dir>
</body>
</html>

Js代碼:函數

angular.module('myApp', [])
    .controller("myCtrl", function($scope) {})
    .directive("myDir", function() {  
        return {  
            template: "<h1>這是自定義指令</h1>",
            restrict: "E"  
        }  
    });

在線代碼演示post

這是一個很簡單的指令小案例,指令中的template模板內容「這是自定義指令」經過<my-dir>標籤引入到頁面中,涉及到指令兩個很重要的參數templaterestrict,下面會具體講解指令的參數。ui

三、Angular Directive參數

參數 類型
restrict String
priority Number
terminal Boolean
template String or Template Function
templateUrl String or Template Function
replace Boolean or String
transclude Boolean
scope Boolean or Object
controller String or function(scope, element, attrs, transclude, otherInjectables) { ... }
controllerAs String
require String
link function(scope, iElement, iAttrs) { ... }
compile function(tElement, tAttrs, transclude) {
return {
pre: function(scope, iElement, iAttrs, controller) { ... },
post: function(scope, iElement, iAttrs, controller) { ... }
}
return function postLink(...) { ... }
}

已上是指令的全部參數以及類型,重點介紹經常使用參數restrict、scope、replace、template、templateUrl、linkspa

參數1:restrict

restrict用於指定directive的使用形式。如上文例子指令代碼中的restrict: "E"即指名使用Element元素形式,同時還有A、C、M共四種形式。默認值爲EA翻譯

參數值 形式 示例
E 元素 Element <my-dir></my-dir>
A 屬性 Attribute <div my-dir></div>
C 樣式 Class <div class="my-dir"></div>
M 註釋 Comment <!-- directive:my-dir -->

Html代碼:設計

<body>
    <!-- 元素形式引入指令 -->
    <my-dir></my-dir>
    <!-- 屬性形式引入指令 -->
    <div my-dir></div>
    <!-- 樣式形式引入指令 -->
    <div class="my-dir"></div>
    <!-- 指令形式引入指令 -->
    <!-- directive: my-dir -->
</body>

Js代碼:

angular.module('myApp', [])
    .controller("myCtrl", function($scope) {})
    .directive("myDir", function() {  
        return {  
            template: "<h1>這是自定義指令</h1>",
            restrict: "ECMA"
        }  
    });

在線代碼演示

指令命名需使用駝峯模式,Html引入指令需使用「-」鏈接每一個單詞,如指令myDir在引入指令時改成my-dir寫法。E、A、C、M四種形式可組合使用。

說明:
在線代碼演示中註釋引入<!-- directive: my-dir -->沒有成功引入指令內容,這是因爲在線codepen不支持,放到本身IDE中是沒問題的。

參數2:scope

scope是指令的做用域,每一個指令建立後能夠繼承父做用域(外部controller提供的做用域或根做用域$rootScope),也能夠擁有本身獨立的做用域,scope參數有三種,分別是:false、true、{},默認爲false

scope = false

scope設置爲false時,指令模板中能夠直接使用父做用域中得變量和函數,舉個例子:

Html代碼:

<div ng-app="MyApp">
    <div class="container" ng-controller="MyController">
        <div class="my-info">個人名字是:<span ng-bind="name"></span>
        <!-- 使用"ng-bind"防止網絡狀態不佳時出現沒有被賦值表達式   -->
            <br/>個人年齡是:<span ng-bind="age"></span>

        </div>
        <!-- 使用屬性聲明指令 -->
        <div class="my-dir" my-dir></div>
    </div>
</div>

Js代碼:

angular.module("MyApp", [])
    .controller("MyController", function ($scope) {
        // 這裏咱們在做用域裏初始化兩個變量
    $scope.name = "echeverra";
    $scope.age = 20;
        // 建立一個方法,修改咱們建立的對象的年齡
    $scope.changeAge = function () {
        $scope.age = 22;
    }
})
    // 建立咱們的指令,指令名字爲"myDir"
    .directive("myDir", function () {
    var obj = {
        // 指令的聲明模式爲 "AE" 屬性和元素
        restrict: "AE",
        // 指令繼承父做用域的屬性和方法
        scope: false,
        replace: true,
        template: "<div class='my-directive'>" +
            "<h3>下面部分是咱們建立的指令生成的</h3>" +
            "個人名字是:<span ng-bind='name'></span><br/>" +
            "個人年齡是:<span ng-bind='age'></span>" +
            "<input type='text' ng-model='name'>"+
            " </div>"
    }
    return obj;
});

在線代碼演示

從在線代碼演示效果中咱們看到建立的指令繼承了父做用域controller中的屬性name、age,一樣也繼承了方法changeAge。當改變指令模塊中inputname會發現父做用域controller中的name也會發生變化,這說明指令的做用域和父做用域在同一個做用域下。

scope = true

scope參數改成true時,咱們再來看:

在線代碼演示

一樣指令中繼承了父做用域的屬性和方法,不一樣的是,當修改指令inputname時,指令的name對應改變,父做用域controller中的name並未發生改變,這說明指令的做用域和父做用域不在同一個做用域下。

scope = {}

scope設置爲{}會使指令的做用域變得更加靈活,修改以前的代碼:

Html代碼:

<div ng-app="MyApp">
    <div class="container" ng-controller="MyController">
        <div class="my-info">個人名字是:<span ng-bind="name"></span>

            <br/>個人年齡是:<span ng-bind="age"></span>
            <br />
        </div>
        <div class="my-dir" my-dir my-name="{{name}}" age="age"  change-my-age="changeAge()"></div>
    </div>
</div>

Js代碼:

angular.module("MyApp", [])
    .controller("MyController", function ($scope) {
    $scope.name = "echeverra";
    $scope.age = 20;
    $scope.male = 'man';
    $scope.changeAge = function(){
        $scope.age = 0;
    }
})
    .directive("myDir", function () {
    var obj = {
        restrict: "AE",
        scope: {
            name: '@myName',
            age: '=',
            changeAge: '&changeMyAge'
        },
        replace: true,
        template: "<div class='my-dir'>" +
            "<h3>下面部分是咱們建立的指令生成的</h3>" +
            "個人名字是:<span ng-bind='name'></span><br/>" +
            "個人性別是:<span ng-bind='male'></span><br/>" +
            "個人年齡是:<span ng-bind='age'></span><br/>" +
            "在這裏修更名字:<input type='text' ng-model='name'><br/>" +
            "<button ng-click='changeAge()'>修改年齡</button>" +
            " </div>"
    }
    return obj;
});

在線代碼演示

咱們使用了隔離的做用域,不表明咱們不可使用父做用域的屬性和方法,咱們能夠經過向scope{}中傳入特殊的前綴標識符(即prefix),來進行數據綁定。

咱們能夠經過前綴標識符@, &, =應用到指令中的屬性,如:{name: '@myName', age: '=', changeAge: '&changeMyAge'}age: '='age: '=age'的簡寫),咱們能夠在<div class="my-directive" my-directive my-name="{{name}}" age="age" change-my-age="changeAge()"></div>這個元素中,經過使用屬性my-name,age,change-my-age來引用這些屬性的值。

首先能夠肯定的是scope={}不會繼承父做用域,由於個人性別項male值是空的,在scope中未進行屬性male綁定。

那麼前綴標識符@, &, =分別有什麼做用呢?

@

這是一個單向數據綁定的前綴標識符。在元素中使用屬性,如:<div my-dir my-name="{{name}}"></div>,屬性的名字要用「-」將兩個單詞鏈接,由於是數據的單向綁定因此要經過使用{{}}來綁定數據。在線代碼演示結果中修改inputname值可看出,只指令的做用域生效name發生改變,父做用域name未改變,這點和scope參數爲true效果是同樣的。

=

這是一個雙向數據綁定的前綴標識符。在元素中使用屬性,如:<div my-dir age="age"></div>,由於是數據的雙向綁定,因此使用=前綴實現,而不是{{}}。在線代碼演示結果中修改inputname值可看出,指令的做用域和父做用域name均發生改變,這點和scope參數爲false效果是同樣的。

&

這是一個綁定函數方法的前綴標識符。在元素中使用屬性,如:<div my-dir change-my-age="changeAge()"></div>,屬性的名字要用「-」將每一個單詞鏈接。在線代碼演示結果中點擊「修改年齡」按鈕,指令做用域和父做用域中的年齡都發生改變,這點和前綴標識符=效果相同。

注意:
在新建立指令的做用域對象中,使用屬性的名字進行綁定時,要使用駝峯命名標準,以下面的代碼:

scope: {
            // `myName` 就是原來元素中的`my-name`屬性
            name: '@myName', 
            age: '=',
            // `changeMyAge`就是原來元素中的`change-my-age`屬性
            changeAge: '&changeMyAge' 
        }

進一步說明,咱們建立的指令是如何利用這些前綴標識符來尋找咱們想要的屬性或函數的呢?

使用@時當指令編譯到模板的name,就會到scope中查找是否含有name的鍵值對(name:'@myName'),找到後發現前綴標識符@就知道這是一個單向數據綁定標識符,以後會去尋找元素上含有這個值的屬性(my-name={{name}}),而後在父做用域中查找{{name}}的值,獲得以後傳遞給屬性my-name,經過指令參數scope中鍵值對name:'@myName'最後傳給了模板中的name。(此處比較繞,不過真心值得研究一番,有助於深入理解)。=&@差很少,只不過=進行的是雙向的數據綁定,不論模板仍是父做用域上的屬性的值發生改變都會使另外一個值發生改變,而&是綁定函數而已。

總結:
scope = falsescope = true初始化都會繼承父做用域中的屬性和方法,不一樣的是scope = false和父做用域在同一做用域下,scope = true和父做用域不在同一做用域,而是建立了一個獨立的做用域。
scope = {}不會繼承父做用域,屬性控制更加靈活,單向綁定@,外部scope可以影響內部scope,但反過來不成立,雙向綁定=,外部scope和內部scopemodel可以相互改變,函數綁定&把內部scope的函數和外部scope的函數綁定起來。

參數3:template

template參數值能夠是一個字符串也能夠是一個函數,值爲字符串比較好理解,上文一直在演示的就是值爲字符串的形式。當值爲函數時,可接收兩個參數elementattrs。舉例:

Html代碼:

<!-- 引入指令 -->
<my-dir name="echeverra" age="20" title="指令元素"></my-dir>

Js代碼:

angular.module('myApp', [])
    .controller("myCtrl", function($scope) {})
    .directive("myDir", function() {  
        return {  
            template: function(element, attrs) {
              return '<h2>name:'+attrs.name+'</h2>'+
                   '<h2>age:'+attrs.age+'</h2>'+
                   '<h2>title:'+attrs.title+'</h2>'
            },
            restrict: "E"  
        }  
    });

在線代碼演示

函數參數element是指使用此指令的元素<my-dir name="echeverra" age="20" title="指令元素"></my-dir>,而attrs是指令元素上全部屬性的集合,形如:

{
  name: 'echeverra',
  age: '20',
  title: '指令元素'
}

參數4:templateUrl

templateUrl和上一個參數template用法基本相同,只不過template值爲字符串是一段html模板,templateUrl值爲字符串是文件路徑。如:

angular.module('myApp', [])
    .controller("myCtrl", function($scope) {})
    .directive("myDir", function() {  
        return {  
            templateUrl: 'index.html';
        }  
    });

具體使用請參考參數template

參數5:replace

replace是一個可選參數,默認值爲false,表示指令中模板template內容會當作子元素插入到指令元素中,若是設置爲true,表示指令中模板會直接替換掉指令元素。

舉例replacefalse:

Html代碼:

<div id="dir">
    <!-- 引入指令 -->
    <my-dir></my-dir>
</div>
<button ng-click='getDirCont()'>獲取引入指令元素</button>
<div ng-bind="dirCont"></div>

Js代碼:

angular.module('myApp', [])

        .controller("myCtrl", function($scope) {
            $scope.getDirCont = function() {
                $scope.dirCont = document.getElementById('dir').innerHTML;
            }
        })
        .directive("myDir", function() {
            return {
                template:"<h1>這是自定義指令</h1>",
                restrict: "E",
                replace: false,
            }
        });

在線代碼演示

點擊獲取引入指令元素按鈕後顯示<my-dir><h1>這是自定義指令</h1></my-dir>,能夠看到指令元素<my-dir></my-dir>還在,若是將replace設置爲true,咱們在來看:

在線代碼演示

此次獲取只顯示元素<h1>這是自定義指令</h1>,說明指令元素<my-dir></my-dir>被替換掉了,這就是二者的區別。

參數6:priority

當同一個元素聲明兩個指令,須要設定執行前後順序,這時就須要用到參數priority,值爲數字,默認值爲0,咱們來看下不使用priority的狀況會有什麼麻煩。

Html代碼:

<!-- 引入指令 -->
<div ng-init="greeting='Hello '" d1 d2>{{greeting}}!</div>

Js代碼:

angular.module('myApp', [])
        .controller("myCtrl", function($scope) {})
        .directive("d1", function() {
            return {
                link: function (scope) {
                    scope.greeting += 'World ';
                }
            }
        })
        .directive("d2", function() {
            return {
                link: function (scope) {
                    scope.greeting += 'Angular ';
                }
            }
        });

在線代碼演示

輸出:Hello Angular World !,可見先執行了ng-init="greeting='Hello '",而後執行了指令d2,最後是d1。實際咱們想預期輸出的是Hello World Angular !,咱們將指令元素中的d一、d2順序對調,再將指令中建立的d一、d2兩個指令順序對調,再試發現輸出的仍是Hello Angular World !,這是由於Angular是使用字母順序來肯定連接函數誰先被調用,將d1改成e1就會獲得預期的結果,有興趣的能夠嘗試一下。如何設定指令執行優先級?這時候priority就派上用場了。

Js代碼:

angular.module('myApp', [])
        .controller("myCtrl", function($scope) {})
        .directive("d1", function() {
            return {
                priority: 1,
                link: function (scope) {
                    scope.greeting += 'World ';
                }
            }
        })
        .directive("d2", function() {
            return {
              priority: 2,
                link: function (scope) {
                    scope.greeting += 'Angular ';
                }
            }
        });

在線代碼演示

輸出:Hello World Angular

參數7:link

link函數主要用於操做Dom元素,給Dom元素綁定事件和監聽,link參數要求聲明一個函數,稱之爲鏈式函數。

寫法:

link: function(scope, element, attrs) {
  // 在這裏操做DOM
}
  • scope:指令所在的做用域
  • element:指令元素的封裝,能夠調用angular封裝的簡裝jq方法和屬性
  • attr:指令元素的屬性的集合

若是指令使用了require選項,那麼連接函數會有第四個參數,表明控制器或者所依賴的指令的控制器。

require 'SomeController',
link: function(scope, element, attrs, SomeController) {
  // 在這裏操做DOM,能夠訪問require指定的控制器
}

下面舉例操做Dom元素,改變元素樣式。

Html代碼:

<!-- 引入指令 -->
<input ng-model="color" placeholder="請輸入顏色值"/>
<br/>
<my-dir name="my-dir-name"></my-dir>

Js代碼:

angular.module('myApp', [])
        .controller("myCtrl", function($scope) {})
        .directive("myDir", function() {
            return {
                template:'<h3 style="background-color:{{color}}">code</h3>',
                replace:true,
                link:function(scope, ele, attrs, ctrl) {
                    ele.bind('click', function () {
                        scope.$apply(function () {
                            scope.color = 'red';
                            ele.text(attrs.name);
                        })
                    });
                    ele.bind('mouseover', function () {
                        ele.css({'cursor': 'pointer'})
                    });
                }
            }
        });

在線代碼演示

點擊code,獲取指令元素的屬性name值,ele調用text()展現,同時ele綁定bind鼠標懸浮事件,調用css()方法,改變鼠標樣式。ele是指令元素的jqLite包裝,因此能夠基本的jq方法,如例子中的bind()css()等。

參數8:transclude

TODO

相關文章
相關標籤/搜索