angular directive詳解
在前端開發的過程中,很多時候我們都希望能夠重複的使用某一個模組,比如說檔案上傳、組織架構樹等,angular的directive可以幫助我們輕鬆的實現元件的重複使用。
首先先讓我們解釋一個問題:什麼是directive?
directive是DOM元素的一個標記(可以是屬性、元素名稱、類名和文字內容),告訴angular的compiler給這個元素新增指定的行為,或者改變這個元素及其子元素。
angular提供了很多內建的directive(比如ngBind、ngModel和ngClass)方便我們使用,就像可以建立controller和service一樣,我們也可以建立自己的directive。
下面讓我們建立一個directive,這個directive僅僅用一個靜態的模板替換它的內容。
index.html
<divng-controller="Controller"><divmy-customer></div></div>
script.js
angular.module('docsSimpleDirective',[]).controller('Controller',['$scope',function($scope){
$scope.customer ={
name:'Naomi',
address:'1600 Amphitheatre' };}]).directive('myCustomer',function(){return{template:'Name: {{customer.name}} Address: {{customer.address}}'};});
展示結果
Name: Naomi Address: 1600 Amphitheatre。
當然,很多時候我們要在directive中展示的內容,絕不僅僅是一個靜態的文字,可能是一顆組織架構樹,有著複雜的樣式,這時候將html程式碼寫在directive的template屬性後就顯得程式碼很臃腫,我們可以使用templateUrl屬性,後面跟上需要載入的模板路徑,例如示例所示:
index.html
<divng-controller="Controller"><divmy-customer></div></div>
script.js
angular.module('docsTemplateUrlDirective',[]).controller('Controller',['$scope',function($scope){
$scope.customer ={
name:'Naomi',
address:'1600 Amphitheatre'};}]).directive('myCustomer',function(){return{
templateUrl:'my-customer.html'};});
my-customer.html
Name:{{customer.name}}Address:{{customer.address}}
需要說明的是,templateUrl後面可以是一個方法,方法返回需要載入的模板路徑,例如:
.directive('myCustomer',function(){return{
templateUrl:function(elem, attr){return'customer-'+attr.type+'.html';}};});
該方法後面可以跟兩個引數,elem代表匹配到directive的元素,attr代表和元素關聯的物件。
當我們建立一個directive,這個directive預設按照元素和屬性進行匹配,我們可以通過restrict屬性進行匹配設定。
restrict
屬性可以設定為:
'A'
- 只匹配屬性名稱'E'
- 只匹配元素名稱'C'
- 只匹配樣式名稱
如果需要可以結合使用:
'AEC'
- 同時匹配屬性、元素和樣式名稱。
.directive('myCustomer',function(){return{
restrict:'E',
templateUrl:'my-customer.html'};});
我們上面的directive寫的很棒,但是有一個致命的缺陷,就是我們的directive不能重複使用,換句話說,就是在一個指定的scope中,由於directive直接訪問controller中的customer,所以directive替換之後的結果是一樣,通過下面的示例簡單說明一下:
index.html
<divng-controller="NaomiController"><my-customer></my-customer></br><my-customer></my-customer></div>
展示結果:
Name: Naomi Address: 1600 Amphitheatre。
Name: Naomi Address: 1600 Amphitheatre。
解決問題的方法是將directive中的scope與外面的scope隔離,同時將需要在directive中使用的值對映到directive的scope中,我們將這種解決方法稱為isolate scope,下面,我們還是通過一個示例簡單演示一下:
index.html
<divng-controller="Controller"><my-customerinfo="naomi"></my-customer><hr><my-customerinfo="igor"></my-customer></div>
script.js
angular.module('docsIsolateScopeDirective',[]).controller('Controller',['$scope',function($scope){
$scope.naomi ={ name:'Naomi', address:'1600 Amphitheatre'};
$scope.igor ={ name:'Igor', address:'123 Somewhere'};}]).directive('myCustomer',function(){return{
restrict:'E',
scope:{
customerInfo:'=info'},
templateUrl:'my-customer-iso.html'};});
my-customer-iso.html
Name:{{customerInfo.name}}Address:{{customerInfo.address}}
展示結果
Name: Naomi Address: 1600 Amphitheatre
Name: Igor Address: 123 Somewhere在index.html中我們將naomi(定義在controller中:$scope.naomi)繫結在info屬性上面,在我們的directive中,我們通過customerInfo: '=info',將naomi繫結到customerInfo中,因此我們可以在template中通過customerInfo訪問使用者資訊。
scope:{// same as '=customer'
customer:'='},
這是一種簡寫形式,等價於customer: '=customer'。
angular的宗旨之一是將業務邏輯和頁面展示分離,業務邏輯放在controller中,頁面展示放在template中,controller中是不推薦直接操作DOM的,所有的DOM操作應該放在directive中進行,下面我們還是用一個簡單的示例演示如何在directive中操作DOM,該示例在頁面中顯示一個時鐘,該時鐘每一秒更新一下時間。
index.html
<divng-controller="Controller">
Date format: <inputng-model="format"><hr/>
Current time is: <spanmy-current-time="format"></span></div>
script.js
angular.module('docsTimeDirective',[]).controller('Controller',['$scope',function($scope){
$scope.format ='M/d/yy h:mm:ss a';}]).directive('myCurrentTime',['$interval','dateFilter',function($interval, dateFilter){function link(scope, element, attrs){var format,
timeoutId;function updateTime(){
element.text(dateFilter(newDate(), format));}
scope.$watch(attrs.myCurrentTime,function(value){
format = value;
updateTime();});
element.on('$destroy',function(){
$interval.cancel(timeoutId);});// start the UI update process; save the timeoutId for canceling
timeoutId = $interval(function(){
updateTime();// update DOM},1000);}return{
link: link
};}]);
前面我們學習了可以通過isolate scope傳遞值或者物件供directive使用,但有些時候我們需要傳遞整個模板,這時候我們需要使用transclude屬性選項,考慮下面程式碼的輸出結果是什麼:
index.html
<divng-controller="Controller"><my-dialog>Check out the contents, {{name}}!</my-dialog></div>
script.js
angular.module('docsTransclusionExample',[]).controller('Controller',['$scope',function($scope){
$scope.name ='Tobias';}]).directive('myDialog',function(){return{
restrict:'E',
transclude:true,
scope:{},
templateUrl:'my-dialog.html',
link:function(scope, element){
scope.name ='Jeff';}};});
my-dialog.html
<divclass="alert"ng-transclude></div>
我們在controller和directive的link方法中都定義了name屬性,根據前面講的isolate scope的知識,似乎顯示的結果應該是Jeff,但是,如果你這樣想就錯啦,因為transclude屬性修改了scope的嵌入方式,使得在directive的內容中能夠訪問所有外部的scope變數而不是內部的scope變數,所以最後的顯示結果為:
Check out the contents, Tobias!
下面我們在對話方塊中新增一個按鈕,允許使用者繫結特定的行為:
index.html
<divng-controller="Controller">
{{message}}
<my-dialogng-hide="dialogIsHidden"on-close="hideDialog(message)">
Check out the contents, {{name}}!
</my-dialog></div>
script.js
angular.module('docsIsoFnBindExample',[]).controller('Controller',['$scope','$timeout',function($scope, $timeout){
$scope.name ='Tobias';
$scope.message ='';
$scope.hideDialog =function(message){
$scope.message = message;
$scope.dialogIsHidden =true;
$timeout(function(){
$scope.message ='';
$scope.dialogIsHidden =false;},2000);};}]).directive('myDialog',function(){return{
restrict:'E',
transclude:true,
scope:{'close':'&onClose'},
templateUrl:'my-dialog-close.html'};});
my-dialog-close.html
<divclass="alert"><ahrefclass="close"ng-click="close({message: 'closing for now'})">×</a><divng-transclude></div></div>
展示結果:
Check out the contents, Tobias!在上面的示例中,當我們點選×,通過ng-click呼叫close方法,'&'允許在directive中呼叫方法close,但是在close方法註冊的源scope中執行,也就是在controller中執行hideDialog方法,此外可以通過鍵值對的形式向directive外部傳遞引數,如示例中所示ng-click="close({message: 'close for now'})",此時可以在hideDialog方法中訪問message變數。
下面我們建立一個directive,這個directive允許使用者拖拽元素,讓我們以此來說明如何在directive中建立事件監聽。
index.html
<spanmy-draggable>Drag ME</span>
script.js
angular.module('dragModule',[]).directive('myDraggable',['$document',function($document){return{
link:function(scope, element, attr){var startX =0, startY =0, x =0, y =0;
element.css({
position:'relative',
border:'1px solid red',
backgroundColor:'lightgrey',
cursor:'pointer'});
element.on('mousedown',function(event){// Prevent default dragging of selected contentevent.preventDefault();
startX =event.pageX - x;
startY =event.pageY - y;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);});function mousemove(event){
y =event.pageY - startY;
x =event.pageX - startX;
element.css({
top: y +'px',
left: x +'px'});}function mouseup(){
$document.off('mousemove', mousemove);
$document.off('mouseup', mouseup);}}};}]);
最後讓我們建立一個directive,此directive根據我們點選的tab展示不同的內容。
index.html
<my-tabs><my-panetitle="Hello"><h4>Hello</h4><p>Lorem ipsum dolor sit amet</p></my-pane><my-panetitle="World"><h4>World</h4><em>Mauris elementum elementum enim at suscipit.</em><p><ahrefng-click="i = i + 1">counter: {{i || 0}}</a></p></my-pane></my-tabs>
script.js
angular.module('docsTabsExample',[]).directive('myTabs',function(){return{
restrict:'E',
transclude:true,
scope:{},
controller:function($scope){var panes = $scope.panes =[];
$scope.select=function(pane){
angular.forEach(panes,function(pane){
pane.selected =false;});
pane.selected =true;};this.addPane =function(pane){if(panes.length ===0){
$scope.select(pane);}
panes.push(pane);};},
templateUrl:'my-tabs.html'};}).directive('myPane',function(){return{require:'^myTabs',
restrict:'E',
transclude:true,
scope:{
title:'@'},
link:function(scope, element, attrs, tabsCtrl){
tabsCtrl.addPane(scope);},
templateUrl:'my-pane.html'};});
my-tabs.html
<divclass="tabbable"><ulclass="nav nav-tabs"><ling-repeat="pane in panes"ng-class="{active:pane.selected}"><ahref=""ng-click="select(pane)">{{pane.title}}</a></li></ul><divclass="tab-content"ng-transclude></div></div>
my-pane.html
divclass="tab-pane"ng-show="selected"ng-transclude></div>
當我們在directive中使用require屬性,如果沒有找到指定的controller,$compile將會報錯,^字首指明在父controller中尋找,沒有^字首則在自己的元素中查詢。
如果directive中指定了require屬性,那麼可以在其link方法中將該controller作為第四個引數傳入。如果需要多個controller,那麼require屬性後面可以跟一個數組,同樣link方法的第四個引數傳入也是一個數組,
傳入需要的controller列表。