AngularJS中除了內置指令,還可以自定義指令。自定義指令和自定義過濾器一樣,有兩種方法:
第一種,在module中配置:$compileProvider.directive('directiveName', function(){ });
代碼模版爲:
$compileProvider.directive('', ['', function(){ // Runs during compile return { // name: '', // priority: 1, // terminal: true, // scope: {}, // {} = isolate, true = child, false/undefined = no change // controller: function($scope, $element, $attrs, $transclude) {}, // require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements // restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment // template: '', // templateUrl: '', // replace: true, // transclude: true, // compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})), link: function($scope, iElm, iAttrs, controller) { } };
第二種,.directive('directiveName', function(){ });
代碼模版爲:
.directive('', ['', function(){ // Runs during compile return { // name: '', // priority: 1, // terminal: true, // scope: {}, // {} = isolate, true = child, false/undefined = no change // controller: function($scope, $element, $attrs, $transclude) {}, // require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements // restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment // template: '', // templateUrl: '', // replace: true, // transclude: true, // compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})), link: function($scope, iElm, iAttrs, controller) { } }; }]);
可以看到,定義指令會返回一個對象,這個對象裏面包含了各個屬性(選項),這些屬性(選項)就是用來定義指令的。
指令的名字不要和內置指令衝突,如果指令的名字爲xxx-yyy,那麼設置指令的名字時應爲xxxYyy,即駝峯式的命名。
restrict: 描述指令在模版中的使用方式,包括:元素、樣式類、屬性、註釋,或者以上幾種方式的任意組合。
template: 以字符串的形式編寫一個內聯模板。
templateUrl: 加載模版所需要使用的url,如果已經指定了template,此屬性會被忽略。
replace: 如果該屬性爲true,則替換指令所在的元素;如果爲false或者不指定,則追加到元素內部。
例子:
<!DOCTYPE html> <html ng-app="firstMoudule"> <head> <meta charset='utf-8'> </head> <body ng-controller="firstController"> <!-- 使用自定義指令first-tag --> <div first-tag></div> <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script> <script type="text/javascript"> angular.module('firstMoudule', [], function($compileProvider, $controllerProvider) { $compileProvider.directive('firstTag', function() { return { restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment template: '<div>hello pomelo!</div>', replace: true }; }); $controllerProvider.register('firstController', function() {}); }); </script> </body> </html>
transclude: 當此屬性爲true時,把指令元素中原來的子節點移動到一個新模板的內部。
例子:
<!DOCTYPE html> <html ng-app="firstMoudule"> <head> <meta charset='utf-8'> </head> <body ng-controller="firstController"> <div first-tag> old data </div> <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script> <script type="text/javascript"> angular.module('firstMoudule', [], function($compileProvider, $controllerProvider) { $compileProvider.directive('firstTag', function() { return { restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment /*transclude爲true時,old data會被放到具有ng-transclude屬性的地方,也就是下面的span*/ template: '<div>new data <span ng-transclude></span> </div>', replace: true, transclude: true }; }); $controllerProvider.register('firstController', function() {}); }); </script> </body> </html>
輸出
priority: 設置指令在模板中的優先級,用整數來表示,數字大的優先級高,先執行。執行順序是相對於元素上的其它指令而言的。如果兩個指令的該值相同,則先定義的先執行。比如內置的ng-repeat該值爲1000。
terminal: 和priority配合使用。如果此屬性爲true,那麼priority比它小的都不會再執行。
例子:
<!DOCTYPE html> <html ng-app="firstMoudule"> <head> <meta charset='utf-8'> </head> <body ng-controller="firstController"> <!-- 同時使用兩個指令 --> <div first-tag second-tag></div> <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script> <script type="text/javascript"> angular.module('firstMoudule', [], function($compileProvider, $controllerProvider) { $compileProvider.directive('firstTag', function() { return { restrict: 'A', priority: 10 }; }); $compileProvider.directive('secondTag', function() { return { template: '<div>data</div>', replace: true, transclude: true, priority: 20, terminal: true }; }); $controllerProvider.register('firstController', function() {}); }); </script> </body> </html>
注意,這裏同時使用兩個指令,只能有一個裏面template有內容,否則將出錯。second-tag優先級較高,先執行,並且terminal爲true,first-tag不會執行。
complie、link:雖然template的方式很有用,但對於指令來說,真正有趣的發生在complie和link函數中。這兩個函數是根據Angular創建動態視圖的兩個處理階段來命名的。Angular的初始化過程爲:
1.加載腳本 加載Angular庫,查找ng-app指令,從而找到應用的邊界。
2.編譯階段 遍歷DOM結構,標識出模版中註冊的所有指令。對於每一條指令,如果存在complie函數,則調用complie函數得到一個編譯好的template函數,template函數又會調用從所有指令收集來的link函數。編譯階段就是負責模板的轉換。
3.鏈接階段 爲了讓視圖變成動態的,Angular會對每一條指令運行一個link函數。link函數負責在model和view之間進行動態關聯。
complie函數僅僅在編譯階段運行一次,而link函數對於指令的每一個實例,都會執行一次。
對於我們會編寫的大多數指令來說,並不需要對模板轉換,只有編寫link函數即可。有complie函數就不用再定義link函數了。
complie函數的語法爲:
這裏返回的相當於link函數。
compile: function(tElement, tAttrs,transclude) { return { pre: function preLink() { }, post: function postLink() { } }; }
tElement是當前指令所在的jQuery對象。tAttrs是指令上定義的參數,比如指令fisrt-tag="123",則tAttrs爲123 。這裏transclude是一個函數,如果需要對內容進行變換,而簡單的基於模板的變換並沒有提供這種功能,那麼可以自己寫這個函數。
如果直接返回,則返回的是postLink,如下:
compile: function(tElement, tAttrs,transclude) { return function() { }; }
preLink在編譯階段之後,指令鏈接子元素之前運行。postLink在所有的子元素指令都鏈接後才運行。如果需要修改DOM結構,應該在postLink裏面做這件事情,如果在preLink裏面做則會破壞綁定過程,並導致錯誤。
例子
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body ng-app="app"> <div ng-controller="Controller1"> <div tag1 tag2></div> </div> <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script> <script type="text/javascript"> angular.module('app', [], function($compileProvider) { $compileProvider.directive('tag1', function() { return { restrict: 'A', template: '<div>hello pomelo!</div>', replace: true, compile: function(tElement, tAttrs, transclude) { console.log('tag1 complie...'); return { pre: function preLink() { console.log('tag1 preLink...'); }, post: function postLink() { console.log('tag1 postLink...'); } }; } }; }); $compileProvider.directive('tag2', function() { return { restrict: 'A', replace: false, compile: function(tElement, tAttrs, transclude) { console.log('tag2 complie...'); return { pre: function preLink() { console.log('tag2 preLink...'); }, post: function postLink() { console.log('tag2 postLink...'); } }; } }; }); }) .controller('Controller1', function() {}); </script> </body> </html>
controller、controllerAs、require:controller會暴露一個API,通過這個API可以在多個指令之間通過依賴注入進行通信。controllerAs是給controller起一個別名,方便使用。require可以將其它指令傳遞給自己。
例子:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body ng-app="app"> <div ng-controller="Controller1"> <div tag1></div> </div> <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script> <script type="text/javascript"> angular.module('app', [], function($compileProvider) { $compileProvider.directive('tag1', function() { return { restrict: 'A', controller: function($scope) { $scope.data = 'this is the data in controller'; this.Data = 'some Data'; }, controllerlAs: 'Controller', link: function($scope, iElm, iAttrs, Controller) { console.log($scope.data); console.log(Controller.Data); } }; }); }) .controller('Controller1', function() {}); </script> </body> </html>
在多個指令間通信還需要require方法。
例子:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body ng-app="app"> <div ng-controller="Controller1"> <div parent-tag></div> </div> <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script> <script type="text/javascript"> angular.module('app', []) .directive('parentTag', function() { return { restrict: 'ECMA', template: '<div><ul><li ng-repeat="i in players">`i`.`name` `i`.`number`</li></ul><child-tag></child-tag></div>', replace: true, controller: function($scope) { $scope.players = [{ name: 'Mertersacker', number: 4 }, { name: 'Koscielny', number: 6 }, { name: 'Gabriel', number: 5 }]; this.addPlayer = function() { $scope.$apply(function() { $scope.players.push({ name: 'Chambers', number: 21 }); }); } }, controllerAs: 'parentController' }; }) .directive('childTag', function() { return { restrict: 'ECMA', require: '^parentTag', template: '<button>add player Chambers</button>', replace: true, link: function($scope, iElm, iAttrs, parentController) { iElm.on('click', parentController.addPlayer); } } }) .controller('Controller1', function() {}); </script> </body> </html>
scope:指明指令所操控數據的作用域。
如果不指定,scope爲false,會使用指令對應的DOM元素上存在的scope對象;
如果scope爲true,則會創建一個scope,它繼承了外層控制器中的scope,在繼承樹中,位於當前scope對象上方的所有scope對象的值都可以被讀取。
如果scope爲{attributeName:'bindingStratry',... ...}即一個對象時,會創建一個獨立的對象。
對於scope是一個object的情況,有點複雜。此時scope的結構爲:
scope:{
attributeName1:'&bindingStratry1',
attributeName2:'=bindingStratry2',
attributeName3:'@bindingStratry3'
}
當爲&bindingStratry時,表示傳遞一個來自父scope的函數,稍後調用。
例子:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body ng-app="app"> <div ng-controller="Controller1"> <div my-tag obj="players"></div> </div> <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script> <script type="text/javascript"> angular.module('app', []) .directive('myTag', function() { return { restrict: 'ECMA', controller: function($scope) { console.log($scope.myScopeFn());//myScopeFn必須以函數方法使用 }, scope: { myScopeFn: '&obj' } } }) .controller('Controller1', function($scope) { $scope.players = [{ name: 'Mertersacker', number: 4 }, { name: 'Koscielny', number: 6 }, { name: 'Gabriel', number: 5 }]; }); </script> </body> </html>
當爲=bindingStratry時,表示傳遞一個來自父scope的屬性,並且是和父scope中對應屬性雙向綁定的。
例子:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body ng-app="app"> <div ng-controller="Controller1"> <div my-tag obj="players"></div> </div> <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script> <script type="text/javascript"> angular.module('app', []) .directive('myTag', function() { return { restrict: 'ECMA', controller: function($scope) { <!-- 雙向數據綁定,可以操縱父scope的數據,這裏添加一個元素進去 --> $scope.myScopeAttr.push({ name: 'Ozil', number: 11 }); }, scope: { myScopeAttr: '=obj' } } }) .controller('Controller1', function($scope) { $scope.players = [{ name: 'Mertersacker', number: 4 }, { name: 'Koscielny', number: 6 }, { name: 'Gabriel', number: 5 }]; console.log($scope.players); }); </script> </body> </html>
可以看到,父scope中players的數據改變了。=bindingStratry能雙向數據綁定。
當爲@bindingStratry時,表示讀取一個來自父scope的屬性,這個屬性只讀,無法改變。
例子:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body ng-app="app"> <div ng-controller="Controller1"> <!-- 這裏要特別注意,要放在{{ }}裏 --> <div my-tag obj="`players`"></div> </div> <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.2/angular.min.js"></script> <script type="text/javascript"> angular.module('app', []) .directive('myTag', function() { return { restrict: 'ECMA', controller: function($scope) { console.log($scope.myScopeAttr); }, scope: { myScopeAttr: '@obj' } } }) .controller('Controller1', function($scope) { $scope.players = [{ name: 'Mertersacker', number: 4 }, { name: 'Koscielny', number: 6 }, { name: 'Gabriel', number: 5 }]; }); </script> </body> </html>
值得一提的是,@bindingStratry是把當前屬性作爲一個字符串傳遞,所以對象等引用類型傳過來會變成字符串,最好還是傳字符串類型的數據過來。