angular 自定義指令詳解

一:指令的建立

建立module:javascript

var module1 = angular.module('module1',[]);
angular.bootstrap(document.body,['module1']);

建立controller:php

var module1 = angular.module('module1',[]);
module1.controller('ctl1', function($scope) {
   $scope.content = 'i\'m, module 1';
    $scope.name = 'module1';
    $scope.save = function() {
        console.log('is function save');
    };
});
angular.bootstrap(document.body,['module1']);

建立指令(directive):html

// 銜接上面的代碼
module1.directive('testDirective', function() {
    // 將對象return出去
    return{
        restrict: 'E',// 指令類型  E:element A:attribute M:comment C: class
        template: '<div>我是指令生成的內容</div>';
        replace: true, //使用模板替換原始標記  指令內本來的數據將被清空
    }
});
angular.bootstrap(document.body,['module1']);

html引入指令:java

<body>
    <div  ng-controller="ctl1">{{content}}
        <test-directive>這是本來的內容</test-directive>
    </div>
</body>

以上代碼須要注意一下幾點:bootstrap

1.咱們定義的指令名稱是testDirective,可是在html中要寫成test-directive。api

2.咱們把指令裏面的代碼都放在了function中的return裏面,其實return出去的內容就是整個指令對象。數組

3.angular.bootstrap(document.body,['module1']);至關於咱們在html中使用ng-app指令。推薦使用bootstarp而不是ng-app;app

2、指令的屬性

指令的屬性以下:框架

  • name
  • priority
  • terminal
  • restrict
  • template
  • templateUrl
  • replace
  • transclude
  • scope
  • controller
  • link
  • controllerAs
  • require
  • compile

name:就是指令名,對應上面代碼中的testDirectiveionic


 

priority:多個指令設置在同一個元素上的執行優先級,執行順序從低至高:1>2>3.priority的值爲正整數,好比priority: 1


 

terminal: true/false 若是爲true,同一個元素上的其餘指令的優先級高於本指令,其餘指令將中止執行


 

restrict:ngular內置的一些指令,好比ng-app,ng-click之類的,都是以html元素的屬性(atrrbile)的形式來實現的,我在使用ionic框架的時候,裏面有不少自定義的標籤、好比:<ion-content></ion-content>。這也是一個指令,他是經過html元素(element)來實現的。除了這兩個以外,指令還支持class(html標籤的class屬性)、commment(html中的註釋)來實現。

在JS代碼中,restrict能夠有如下賦值:

 restrict: 'AE',// 指令類型  E:element A:attribute M:comment C: class

能夠是多個restrict: 'AEMC',也能夠是一個restrict: 'E'。在html中對應的寫法爲:

字母 聲明風格 事例
E 元素 <my-memu title="products"></my-memu>
A 屬性 <div my-memu="products"></div>
C 樣式 <div class="my-memu:products"></div>
M 註釋 <!-- directive my-memu products -->

template:

同一個指令中只能templatetemplateUrl只能選其一。

template爲模板內容。即你要在指令所在的容器中插入的html代碼。

template屬性接收一個字符串,相似這樣: template: '<div>我是指令生成的內容</div>';


 

templateUrl:

爲從指定地址獲取模板內容。即你要在指令所在的容器中插入的一個.html文件。

有了templateUrl咱們就能夠將想實現的內容寫成一個單獨的html模版,在須要的地方插入

angular.module('module1').run(['$templateCache', function ($templateCache) {
    $templateCache.put('test.html', '<div>This is templateUrl</div>');
}]);
angular.module('module1').directive("testDirective", ["$parse", "$http",
    function ($parse, $http) {
       return {
            restrict: "E",
            templateUrl: "test.html"
        };
}]);

replace:

是否用模板替換當前元素。true : 將指令標籤替換成temple中定義的內容,頁面上不會再有<my-directive>標籤;false :則append(追加)在當前元素上,即模板的內容包在<my-directive>標籤內部。默認false。

var app = angular.module("app", [])
    .directive("hello", function () {
        var option = {
            restrict: "AECM",
            template: "<h3>Hello, Directive</h3>",
            replace: true  //這裏replace爲true,因此原來的內容會被template代替
        };
        return option;
    })
<html>
<head></head>
<body>
<hello>我是原來的內容</hello><!--消失-->
    ===> 變成<h3>Hello, Directive</h3> 若是replace爲false ===><hello><h3>Hello, Directive</h3></hello>
</body>
</html>

transculde:

是否使用ng-transculde來包含html中指令包含的原有的內容,接收兩個參數true/false

var app = angular.module("app", [])
    .directive("hello", function () {
        var option = {
            restrict: "AECM",
            template: "<h3>Hello, Directive</h3><span ng-transclude></span>",
            transculde: true  //這裏transculde爲true,因此原來的內容會被放在有ng-transclude屬性的標籤內
        };
        return option;
    })
<html>
<head></head>
<body>
    <hello>我是原來的內容</hello>
    ===> 變成<hello><h3>Hello, Directive</h3><span ng-transclude>我是原來的內容</span></hello>
</body>
</html>

scope:

directive 默認能共享父 scope 中定義的屬性,例如在模版中直接使用父 scope 中的對象和屬性。一般使用這種直接共享的方式能夠實現一些簡單的 directive 功能。

可是,當你要建立一個能夠重複使用的directive的時候,就不能依賴於父scope了,由於在不一樣的地方使用directive對應的父scope不同。

因此你須要一個隔離的scope,咱們能夠向下面這樣來定義咱們的scope。

module1.directive("testDirective", function () {
    return {
        scope: {
            "":"@",
            "":"&",
            "":"="

        },
        template: 'Say:{{value}}'
    }
});

這樣就很方便的將咱們directive的上下文scope給定義出來了,可是,若是我想將父scope中的屬性傳遞給directive的scope怎麼辦呢?

directive 在使用隔離 scope 的時候,提供了三種方法同隔離以外的地方交互:

  • @ 指令中定一個變量用來獲取父scope中的值,是單項綁定的,指令中的值改變,父scope中的值不變
<script type="text/ng-template" id="scopeTemplate">
    <div class="panel-body">
        <p>Data Value: {{local}}</p>
        <p>Data Value: {{secondLocal}}</p>
    </div>
</script>
<script type="text/javascript">
    angular.module("exampleApp", [])
        .directive("scopeDemo", function () {
            return {
                template: function() {
                    return angular.element(
                        document.querySelector("#scopeTemplate")).html();
                },
                scope: {
                    local: "@nameprop",
                    secondLocal:"@secondNameprop"
                }
            }
        })
        .controller("scopeCtrl", function ($scope) {
            $scope.data = { name: "Adam" };
        });
</script>
<body ng-controller="scopeCtrl">
<div class="panel panel-default">
    <div class="panel-body">
        Direct Binding: <input ng-model="data.name" /> // 這裏改變下面會跟着改變
    </div>
    <div class="panel-body" scope-demo nameprop="{{data.name}}"></div> // 跟着上面數值動態變化
    <div class="panel-body" scope-demo second-nameprop="{{data.name}}"></div> // 跟着上面數值動態變化
</div>
</body>

指令中的local經過nameprop得到外部屬性data.name,因此在控制器中的data.name變化時,指令中的local也會跟着改變;(但若是是隻改變local的值,外界的data.name是不會變的);

  • & 調用父級做用域中的方法(function);
<script type="text/ng-template" id="scopeTemplate">
    <div class="panel-body">
        <p>Name: {{local}}, City: {{cityFn()}}</p>
    </div>
</script>
<script type="text/javascript">
    angular.module("exampleApp", [])
        .directive("scopeDemo", function () {
            return {
                template: function () {
                    return angular.element(
                        document.querySelector("#scopeTemplate")).html();
                },
                scope: {
                    local: "=nameprop",
                    cityFn: "&city"
                }
            }
        })
        .controller("scopeCtrl", function ($scope) {
            $scope.data = {
                name: "Adam",
                defaultCity: "London"
            };
            $scope.getCity = function (name) {
                return name == "Adam" ? $scope.data.defaultCity : "Unknown";
            }
        });
</script>
<body ng-controller="scopeCtrl">
    <div class="panel panel-default">
        <div class="panel-body">
            Direct Binding: <input ng-model="data.name" />
        </div>
        <div class="panel-body" scope-demo
             city="getCity(data.name)" nameprop="data.name"></div>
</div>
</body>
  • = 指令中定一個變量用來獲取父scope中的值,是雙項綁定的,指令中的值改變,父scope中的值不變

這裏全部指令做用域跟外界交互都是經過屬性值傳入的:<div class="panel-body" scope-demo city="getCity(data.name)"nameprop="data.name"></div>


 controller link:

controller: function ($scope, $element, $attrs) {}三個參數
link: function (scope, element, attrs, controller) {}四個參數

第一次嘗試:

<custom-directive></custom-directive>
angular
    .module('app',[])
    .directive('customDirective', customDirective);

function customDirective() {
    var directive = {
        restrict: 'EA',
        template: '<div>{{vm.test}}</div>',
        link: function(){},
        controller: directiveController
    };
    return directive;
}

function directiveController() {
    var vm = this;
    vm.test = "I'm from Controller";
}

第二次嘗試:

angular
    .module('app',[])
    .directive('customDirective', customDirective);

function customDirective() {
    var directive = {
        restrict: 'EA',
        template: '<div>{{test}}</div>',
        link: directiveLink
    };

    return directive;
}


function directiveLink(scope,elem,attr) {
    scope.test = "I'm from Link";
}

到這裏,咱們不只要開始思索:指令中的controller和link均可以實現一樣的效果,那在指令中放這兩個屬性幹嗎?咱們的代碼究竟是放在controller仍是link中?

咱們首先來看看當兩者一塊兒使用時,呈現結果的順序即在編譯先後生成的順序。

angular
    .module('app',[])
    .directive('customDirective', customDirective);

function customDirective() {
    var directive = {
        restrict: 'EA',
        template: '<div>xpy0928{{test}}</div>',
        link: directiveLink,
        controller:directiveController
    };

    return directive;
}

function directiveController($scope){
    $scope.test = " from contrller cnblogs";
}

function directiveLink(scope,elem,attr) {
    scope.test = scope.test + ",and from link cnblogs";
}

咱們由此得出結論:編譯以前執行控制器(controller),編譯以後執行連接(link)。

可是咱們還未從根本上解決問題,在controller和link應該放哪些代碼?咱們接下來再看一個例子:

var app = angular.module('app',[]);
    
app.directive('customDirective', customDirective);

function customDirective() {
    var directive = {
        restrict: 'EA',
        template: '<child-directive><child-directive>',
        controller: function($scope, $element) {
            $element.find('span').text('hello cnblogs!');
        }
    };

    return directive;
}
app.directive("childDirective",childDirective);

function childDirective() {
    var directive = {
        restrict: 'EA',
        template: '<h1>hello xpy0928</h1>',
        replace: true,
        link: function($scope, $element, attr) {
            
            $element.replaceWith(angular.element('<span>' + $element.text() + '</span>'));
        }
    }
    return directive;
}

此時結果應該仍是hello xpy0928仍是hello cnblogs呢?咱們看下結果

咱們再來將如上進行修改看看:

 

var app = angular.module('app',[]);
    
app.directive('customDirective', customDirective);

function customDirective() {
    var directive = {
        restrict: 'EA',
        template: '<child-directive><child-directive>',
        link: function(scope, el) {
            el.find('span').text('hello cnblogs!');
        }
    };

    return directive;
}
app.directive("childDirective",childDirective);

function childDirective() {
    var directive = {
        restrict: 'EA',
        template: '<h1>hello xpy0928</h1>',
        replace: true,
        link: function($scope, $element, attr) {
            
            $element.replaceWith(angular.element('<span>' + $element.text() + '</span>'));
        }
    }
    return directive;
}

爲何會出現如此狀況?由於在controller函數中此時全部child-directive指令中的link函數還未運行因此此時替換無效

 由此咱們能夠基本得出在controller和link中應該寫什麼代碼的結論:

(1)在controller寫業務邏輯(咱們明白業務邏輯大部分是放在服務中),這裏所說的業務邏輯乃是爲呈現視圖以前而準備的數據或者是與其餘指令進行數據交互而暴露這個api。

(2)在link中主要操做DOM。


 

controllerAs:

controller as引入的意義和方法 

<div ng-app="myApp">
    <div ng-controller="firstController">
        <div book-list></div>
    </div>
</div>
angular.module('myApp',[])
.directive('bookList',function(){
    return {
        restrict:'ECAM',
        //此處定義了該指令的controller屬性
        controller:function($scope){
            $scope.books=[
                {name:'php'},
                {name:'javascript'},
                {name:'java'}
            ];
            this.addBook=function(){       //或者 scope.addBook=...
                alert('test');
            }
        },
        controllerAs:'bookListController', //給當前controller起個名稱
        template:'<ul><li ng-repeat="book in books">{{ book.name }}</li></ul>',
        replace:true,
        //link中注入 bookListController ,就可使用它的方法了
        link:function(scope,iElement,iAttrs,bookListController){
            iElement.on('click',bookListController.addBook);
        }
    }
})
.controller('firstController',['$scope',function($scope){
}])

require:

談require選項以前,應該先說說controller選項,controller選項容許指令對其餘指令提供一個相似接口的功能,只要別的指令(甚至是本身)有須要,就能夠獲取該controller,將其做爲一個對象,並取得其中的全部內容。而require就是鏈接兩個指令的鎖鏈,它能夠選擇性地獲取指令中已經定義好的controller,並做爲link函數的第四個參數傳遞進去,link函數的四個參數分別爲scope,element,attr和someCtrl,最後一個就是經過require獲取的controller的名字,對於controller的名字,能夠在指令中用controllerAs選項進行定義,這是發佈控制器的關鍵.

      具體如何獲取controller呢?require選項的值能夠分別用前綴?、^ 和?^進行修飾,也能夠不修飾。

      若是不進行修飾,好比require:'thisDirective',那麼require只會在當前指令中查找控制器

      若是想要指向上游的指令,那麼就是用^進行修飾,好比require:'^parentDirective',若是沒有找到,那就會拋出一個錯誤。

      若是使用?前綴,就意味着若是在當前指令沒有找到控制器,就將null做爲link的第四個參數;

      那麼,若是將?和^結合起來,咱們就能夠既指定上游指令,又能夠在找不到時,不拋出嚴重的錯誤。

    如今問題來了,若是我想指定多於一個指令,那怎麼辦呢?這時,咱們能夠將須要的指令放進一個數組中,例如:require:['^?firstDirective','^?secondDirective','thisDirective'],這不正是依賴注入的形式嗎?但要注意一點,若是使用這種寫法的話,原先指令中發佈的控制器的名字,即controllerAs選項,就失去意義了。此時,咱們在link中調用這些指令的controller的方法變爲:先爲上邊的數組定義一個名字,接着根據其下標號來指定具體的某個指令。相似於:ctrlList[0]。

 

  假如咱們如今須要編寫兩 個指令,在linking函數中有不少重合的方法,爲了不重複本身(著名的DRY原則),咱們能夠將這個重複的方法寫在第三個指令的 controller中,而後在另外兩個須要的指令中require這個擁有controller字段的指令,最後經過linking函數的第四個參數就能夠引用這些重合的方法。代碼的結構大體以下:

var app = angular.modeule('myapp',[]);  
  
app.directive('common',function(){  
    return {  
    ...  
    controller: function($scope){  
        this.method1 = function(){  
        };  
        this.method2 = function(){  
        };  
    },  
    ...  
    }  
});  
  
app.directive('d1',function(){  
    return {  
    ...  
    require: '?^common',  
    link: function(scope,elem,attrs,common){  
        scope.method1 = common.method1;  
        ..  
        },  
    ...  
    }  
});  
相關文章
相關標籤/搜索