angular 自定義指令詳解


一:指令的創建

創建module:

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

創建controller:

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):

// 銜接上面的代碼
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引入指令:

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

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

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

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

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

二、指令的屬性

指令的屬性如下:

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

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


 

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;  
        ..  
        },  
    ...  
    }  
});  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM