這個文檔解釋了何時你可能會想要在你的AngularJS應(yīng)用中創(chuàng)建自定義指令,以及如何實現(xiàn)它們
指令是什么?##
站在高的角度說, 指令是DOM元素上的一些標識符(例如屬性,元素名,注釋或者是css類),它們告訴AngularJS的html 編譯器($compile)來賦予DOM元素一些特定的行為(例如通過事件監(jiān)聽器),甚至是改變DOM元素和它的子節(jié)點
Angular擁有許多內(nèi)置的指令,像ngBind
,ngModel
,以及ngClass
,就像你創(chuàng)建controller和service一樣,你能夠創(chuàng)建你自己的指令,當Angular初始化你的應(yīng)用后,html編譯器遍歷DOM樹以尋找指令并初始化它們
"編譯"html模板是什么意思?對于angularJS來說,"編譯"意味著把指令鏈接到html上使它變得可交互.我們之所以使用專業(yè)術(shù)語"編譯"的原因是鏈接指令的這個循環(huán)過程是編譯型語言中編譯源代碼過程的真實映射
匹配指令##
在我們寫指令之前,我們需要知道Angular的html編譯器是怎樣決定何時來使用那些給定的指令
就像當元素匹配選擇器時所使用的術(shù)語,我們說一個元素匹配一個指令,當這個指令是它聲明的一部分時
在接下來的例子中,我們說<input>
元素匹配ngModel
指令
<input ng-model="foo">
這個<input>
元素同樣匹配ngModel
<input data-ng-model="foo">
這個元素匹配person
指令
<person>{{name}}</person>
標準化命名##
Angular根據(jù)元素的標簽和屬性名來決定哪個元素匹配哪個指令,我們首先想到的是通過小駝峰式的標準化命名(如ngModel
)來區(qū)別指令.然而,由于html是忽略大小寫的,我們在DOM中通過小寫的形式來識別指令,使用有特色的dash-delimited
屬性來描述(如ng-model
)
標準的寫法如下所示:
- 在元素/屬性前添加
x-
或者data-
前綴的形式 - 用
:
,-
,'_'分隔過的命名來替代駝峰寫法
<div ng-controller="Controller">
Hello <input ng-model='name'> <hr/>
<span ng-bind="name"></span> <br/>
<span ng:bind="name"></span> <br/>
<span ng_bind="name"></span> <br/>
<span data-ng-bind="name"></span> <br/>
<span x-ng-bind="name"></span> <br/>
</div>
最佳實踐:最好是使用中劃線的形式(如
ng-bind
表示ngBind),如果你想要使用html驗證工具,你可以使用添加data-
前綴的形式(如data-ng-bind
表示ngBind).上面其他的那些聲明形式出于歷史遺留性原因不會報錯,但是我們建議你避免使用它們
指令類型##
$compile
能夠通過元素名,屬性,類名和注釋來匹配到指令
angular的所有這些指令都是通過屬性,標簽名,注釋和類名這四種形式來匹配的.下面是具體例子
<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>
最佳實踐: 最好是通過標簽名或者屬性來聲明指令,這樣做通常會使元素匹配到相應(yīng)指令更容易
最佳實踐: 注釋型指令通常被使用在這些地方: DOM API限制了創(chuàng)建跨越多個元素的指令的能力(例如在
<table>元素內(nèi)
).angularJS 1.2版本引入了ng-repeat-statrt
和ng-repeat-end
指令來作為這個問題更好的解決方案,開發(fā)者被鼓勵盡可能使用這兩個指令來替代自定義注釋型指令
創(chuàng)建指令##
首先讓我們來看看注冊指令的API, 和controller非常相似,指令被注冊在modules上.為了注冊一個指令,你需要使用module.directive
API,module.directive
接收兩個參數(shù):第一個是標準化的指令命名,第二個是一個工廠函數(shù).這個工廠函數(shù)應(yīng)該返回一個帶有不同配置選項的對象,來告訴$compile
指令有著怎樣的行為以及何時匹配
這個工廠函數(shù)僅僅在編譯器第一次匹配到指令時被解析一次.你能夠在這里做任何初始化工作.這個函數(shù)在解析時同樣使用依賴注入,使得它像控制器一樣變得可注入
我們接下來會看一些普通的例子,然后深度挖掘下不同的配置項以及編譯流程
最佳實踐: 為了避免和將來的標準發(fā)生沖突,最好是給你的自定義指令名加上前綴.如果你創(chuàng)建了一個
<carousel>
指令,如果html7引入了同樣的元素就會發(fā)生問題,引入兩個字母或三個字母的前綴(如btfcarousel)看起來不錯;同樣的,不要給你的自定義指令加上ng
前綴,因為它可能和angular未來版本引入的指令發(fā)生沖突
在接下來的例子中,我們會使用my
作為前綴
模板可擴展的指令##
讓我們假設(shè)你有一大塊模板來表示用戶的信息.這個模板在你的代碼中重復(fù)了許多次,當你在一處更改它時,你不得不在其他的好幾個地方也進行更改.這是一個創(chuàng)建指令來簡化模板的好機會.
讓我們創(chuàng)建一個指令對這些內(nèi)容用靜態(tài)模板來做個簡單的變換.
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}}'
};
});
index.html
<div ng-controller="Controller"> <div my-customer></div></div>
請注意我們在這個指令中所綁定的東西.在
$compile
編譯,以及l(fā)ink函數(shù)把<div my-customer></div>
鏈接到document樹之后,它會試圖匹配當前元素子節(jié)點上的指令.這意味著你能夠組合不同的指令,我們會在接下來的例子中看看如何去做
最佳實踐: 除非你的模板非常小,通常情況下把模板放到html文件中通過
templateUrl
選項來引入會是更好的選擇
如果你很熟悉ngInclude
,會發(fā)現(xiàn)templateUrl
和它很相似.這里是使用templateUrl
的例子:
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'
};
});
index.html
<div ng-controller="Controller"> <div my-customer></div></div>
my-customer.html
Name: {{customer.name}} Address: {{customer.address}}
templateUrl
也可以是一個函數(shù),它返回html模板的url來被指令使用和加載.這個函數(shù)有兩個參數(shù):調(diào)用指令的元素本身element
,以及這個元素上所關(guān)聯(lián)的屬性對象attr
.
你現(xiàn)在還沒有能力在
templateUrl
函數(shù)上獲取到scope變量,這是因為在scope被初始化之前這個模板就已經(jīng)被引用了.
script.js
angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
templateUrl: function(elem, attr) {
return 'customer-' + attr.type + '.html';
}
};
});
index.html
<div ng-controller="Controller"> <div my-customer type="name"></div> <div my-customer type="address"></div></div>
customer-name.html
Name: {{customer.name}}
customer-address.html
Address: {{customer.address}}
注意:當你創(chuàng)建指令時,默認情況下只能識別通過屬性或元素聲明的指令,為了能夠通過譬如類名的方式來創(chuàng)建指令,你需要使用
restrict
選項.
restrict
選項通常是下面這四種:
-
A
-僅僅匹配屬性名 -
E
-僅僅匹配元素名 -
C
-僅僅匹配類名 -
M
-僅僅匹配注釋
這些選擇項是能夠結(jié)合使用的:
-
AEC
-會匹配屬性,元素或者是類名
讓我們使用restrict: 'E'
來更改我們的指令.
script.js
angular.module('docsRestrictDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
templateUrl: 'my-customer.html'
};
});
index.html
<div ng-controller="Controller"> <my-customer></my-customer></div>
my-customer.html
Name: {{customer.name}} Address: {{customer.address}}
我們何時應(yīng)該使用屬性 vs 元素?當你創(chuàng)建一個組件,它完全控制模板時,你應(yīng)該使用元素
element
,一個常見的案例是:當你為你的模板創(chuàng)建一個特定領(lǐng)域的語言;當你為你已經(jīng)存在的元素添加新功能時,你應(yīng)該使用屬性作為聲明方式
在這里使用元素作為指令的聲明方式myCustomer
無疑是正確的選擇.因為你不是在給一個元素添加某些自定義的行為;你是在定義元素作為一個自定義組件的核心行為.
指令的隔離作用域##
我們上面的指令myCustomer
看起來很不錯,但是它有一個致命的瑕疵.我們僅僅只能在一個給定的作用域中使用它一次
在當前的實現(xiàn)中,我們需要每次創(chuàng)建一個不同的控制器來復(fù)用這個指令
script.js
angular.module('docsScopeProblemExample', [])
.controller('NaomiController', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.controller('IgorController', ['$scope', function($scope) {
$scope.customer = {
name: 'Igor',
address: '123 Somewhere'
};
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
templateUrl: 'my-customer.html'
};
});
index.html
<div ng-controller="NaomiController">
<my-customer></my-customer>
</div>
<hr>
<div ng-controller="IgorController">
<my-customer></my-customer>
</div>
my-cutomser.html
Name: {{customer.name}} Address: {{customer.address}}
這顯然不是一個好的解決方案
我們想要有能力去把指令的作用域和外部作用域隔離開來,并且可以把外部作用域傳遞進來.我們能夠通過創(chuàng)建"隔離作用域"來實現(xiàn)它.為了這個目的,我們使用指令的scope
選項.
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'
};
});
index.html
<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
<hr>
<my-customer info="igor"></my-customer>
</div>
my-customer-iso.html
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
讓我們看
index.html
,第一個<my-customer>
元素把info
屬性綁定到naomi
上,我們在控制器的作用域中暴露出這個naomi
對象;第二個元素把info
綁定到igor
讓我們近距離看看作用域選項:
//...
scope: {
customerInfo: '=info'
},
//...
scope
選項是一個對象:它包含一組綁定的屬性用于隔離作用域間的綁定.在這個例子中僅僅只有一個屬性
- 它的名字(
customerInfo
)與指令的隔離作用域中屬性customerInfo
一致 - 它的值(
=info
)告訴$compile
去綁定info
屬性
注意: 這個在指令
scope
選項中的=attr
屬性的命名規(guī)則和指令的命名方式一樣,為了綁定屬性<div bind-to-this="thing">
,你需要這樣聲明=bindToThis
.
如果你想要綁定的屬性名和指令內(nèi)部的變量名一樣的話,你可以采取下面縮寫的形式:
...
scope: {
// same as '=customer'
customer: '='
},
...
除了能把不同的數(shù)據(jù)綁定到指令內(nèi)部的作用域中,使用隔離作用域還有其他的作用.
看接下來這個例子:添加另一個屬性vojta
到我們的控制器作用域中,并且試圖在指令內(nèi)部的模板中獲取它
script.js
angular.module('docsIsolationExample', [])
.controller('Controller', ['$scope', function($scope) {
$scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
$scope.vojta = { name: 'Vojta', address: '3456 Somewhere Else' };
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
templateUrl: 'my-customer-plus-vojta.html'
};
});
index.html
<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
</div>
my-customer-plus-vojta.thml
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
<hr>
Name: {{vojta.name}} Address: {{vojta.address}}
請注意{{vojta.name}}和{{vojta.address}}為空,意味著他們未定義,盡管我們在控制器中定義了
vojta
,在指令內(nèi)部它是不可獲取的就像名字所暗示的那樣,指令的隔離作用域會隔離除了你顯式地在
scope: {}
中添加的哈希對象之外的一切內(nèi)容.這在你創(chuàng)建可復(fù)用組件時是非常有用的,因為它阻止了除了你顯式引入以外的模型來更改你的組件狀態(tài)
最佳實踐: 使用
scope
選項來創(chuàng)建隔離作用域,當你想要在你的app中創(chuàng)建可復(fù)用組件時
創(chuàng)建一個操作DOM的指令##
在這個例子中我們會創(chuàng)建一個展示當前時間的指令.每過一秒,它會更新DOM來展示當前時間
指令如果想要操作DOM,一般是使用link
選項來注冊DOM監(jiān)聽器同時更新DOM,它在模板被復(fù)制以后執(zhí)行,并且指令的具體邏輯代碼會放在這里.
link
接收一個函數(shù)如下所示function link(scope, element, attrs, controller, transcludeFn)
:
-
scope
是Angular 的scope對象 -
element
是指令匹配到的用jqLite
包裹了以后的元素 -
attrs
是一個標準化屬性名的key-value
對象.通過它可以訪問到指令上的全部屬性 -
controller
表示指令自己的控制器或指令引入的控制器實例(如果有的話).確切值依賴于指令的require
選項
在我們的link
函數(shù)中,我們想要每過一秒就更新一次時間,或是無論何時用戶改變指令所綁定的時間格式字符串.我們會使用$interval
服務(wù) ,這會比使用$timeout
更容易,并且也能更好的進行端到端測試(因為在我們完成測試前我們需要確保所有的$timeout
都已經(jīng)執(zhí)行完);我們同時也希望當指令被刪除時移除$interval'
,這樣我們能避免內(nèi)存泄漏
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(new Date(), 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
};
}]);
index.html
<div ng-controller="Controller">
Date format: <input ng-model="format"> <hr/>
Current time is: <span my-current-time="format"></span>
</div>
這里有不少事情需要注意下.就像
module.controller
API,這里的函數(shù)參數(shù)同樣是被依賴注入的,因為這個,我們能夠在link
函數(shù)中使用$interval
和dataFilter
我們注冊了一個event事件element.on('$destroy', ....)
,什么時候會觸發(fā)這個$destroy
事件呢?
在AngularJS的事件冒泡機制中有一些特別的時刻,當被Angular的編譯器編譯過的DOM節(jié)點被銷毀時,它會冒泡一個$destroy
事件;相似的,當一個AngularJS的作用域被銷毀時,它會向那些監(jiān)聽的作用域廣播一個$destroy
事件
通過監(jiān)聽這個事件,你能夠移除可能會導(dǎo)致內(nèi)存泄漏的事件監(jiān)聽器.scope
上注冊的監(jiān)聽器和元素當它們被銷毀時會被自動清除.但如果你是在service,或者是還未被刪除的DOM節(jié)點上注冊了一個監(jiān)聽器,你不得不自己手動銷毀它們,否則會有內(nèi)存泄漏的風(fēng)險
最佳實踐: 你應(yīng)該在指令的自動銷毀機制之外手動地進行銷毀.你能夠使用
element.on('$destroy', ...)
或者是scope.$on('$destroy', ...)
來當指令被移除時執(zhí)行清除函數(shù)
創(chuàng)建一個包裹了其他元素的指令##
我們已經(jīng)見到:你能過通過使用隔離作用域來給指令傳遞一些數(shù)據(jù);但有時比起字符串或者對象,我們更想要能夠傳遞整個模板;讓我們創(chuàng)建一個對話框組件,這個組件應(yīng)該能包裹任意內(nèi)容
為了這個目的,我們使用transclude
選項.
script.js
angular.module('docsTransclusionDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
templateUrl: 'my-dialog.html'
};
});
index.html
<div ng-controller="Controller"> <my-dialog>Check out the contents, {{name}}!</my-dialog></div>
my-dialog.html
<div class="alert" ng-transclude></div>
transclude
選項實際上做了些啥?transclude
使得擁有這個選項的指令的內(nèi)容有能力去獲取指令外部的作用域
為了說明這一點,讓我們看接下來的例子.注意我們在script.js
的link
函數(shù)內(nèi)重新定義了name
為Jeff
.你覺得下面{{name}}
最終會顯示啥?
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) {
scope.name = 'Jeff';
}
};
});
index.html
<div ng-controller="Controller">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
my-dialog.html
<div class="alert" ng-transclude></div>
通常來說,我們可能會以為
{{name}}
的值會是Jeff
,然而在這個例子中我們看到{{name}}
的值仍然是Tobias
transclude
選項改變了作用域嵌套的方式.實際上指令調(diào)用時標簽中所嵌套的內(nèi)容是和外部作用域綁定(而不是內(nèi)部),這使得我們能夠讓內(nèi)容獲取到外部作用域
請注意:如果我們在指令中沒有創(chuàng)建它自己的作用域(這里即scope: false
),我們在指令內(nèi)聲明scope.name="Jeff"
,會更改外部作用域中name
的值,即最后輸出會是Jeff
這種行為對于那些包裹了某些內(nèi)容的指令是很有意義的,否則的話你不得不一個個手動引入你想要使用的值;如果你真的這樣做了,那是不是就意味著你其實無法真正的獲取到任意內(nèi)容了呢?
最佳實踐: 僅僅在你想要創(chuàng)建一個可以包裹任意元素的指令時,使用
transclude:true
下一步,讓我們給這個對話框添加一些按鈕,允許某人使用指令來綁定一些自定義行為的函數(shù)
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'
};
});
index.html
<div ng-controller="Controller">
{{message}}
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
Check out the contents, {{name}}!
</my-dialog>
</div>
my-dialog-close.html
<div class="alert">
<a href class="close" ng-click="close({message: 'closing for now'})">×</a>
<div ng-transclude></div>
</div>
我們想要運行通過指令scope
選項引用進來的函數(shù),但是想讓它運行在它注冊時的上下文環(huán)境中.
我們之前見過了如何在scope
選項中使用=attr
綁定,在上面的例子中我們使用了&attr
綁定.&
綁定允許指令執(zhí)行表達式在它原先的上下文作用域中的值,在某個特定的時機;任何合法的表達式都是允許的,包括包含了函數(shù)調(diào)用的表達式.因為這個原因,&
綁定非常適合指令行為需要綁定函數(shù)回調(diào)的情景.
當用戶點擊了對話框中的x
,指令的close
函數(shù)被調(diào)用,讓我們感謝下ng-click
.這個在隔離作用域中被稱為call
的函數(shù)實際上計算了表達式hideDialog(message)
在它原先的作用域上下文中的值,即運行Controller
的hideDialog
函數(shù)
通常情況下,能夠把隔離作用域中的數(shù)據(jù)通過表達式來傳遞給父級作用域這一特性是相當誘人的.我們能夠通過在指令內(nèi)部給引入的函數(shù)傳入一組key:value
來實現(xiàn)這一特性.例如,hideDialog
函數(shù)獲取到當對話框隱藏時需要展示的信息.這個信息是通過我們在指令內(nèi)部調(diào)用close({message: 'closing for now'})
來聲明,然后本地變量message
會被on-close
表達式獲取到.
最佳實踐: 當你想要你的指令暴露出綁定行為的API時,使用
scope
選項的&attr
綁定.
創(chuàng)建一個添加了事件監(jiān)聽器的指令##
以前我們使用link
函數(shù)來生成一個可操作DOM元素的指令,在這個例子的基礎(chǔ)上,讓我們創(chuàng)建一個指令來實時的反映元素上的事件
不如讓我們來寫一個允許用戶拖動元素的指令吧?
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 content
event.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);
}
}
};
}]);
index.html
<span my-draggable>Drag Me</span>
創(chuàng)建一個可通信的指令##
你能夠在模板中使用任意指令來組合使用它們
有時,你會需要一個由不同指令的結(jié)合構(gòu)建而成的組件
想象一下你需要一個容器,容器內(nèi)激活的tab和對應(yīng)的展示內(nèi)容一致
script.js
angular.module('docsTabsExample', [])
.directive('myTabs', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: ['$scope', function MyTabsController($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'
};
});
index.html
<my-tabs>
<my-pane title="Hello">
<p>Lorem ipsum dolor sit amet</p>
</my-pane>
<my-pane title="World">
<em>Mauris elementum elementum enim at suscipit.</em>
<p><a href ng-click="i = i + 1">counter: {{i || 0}}</a></p>
</my-pane>
</my-tabs>
my-tabs.html
<div class="tabbable">
<ul class="nav nav-tabs">
<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">
<a href="" ng-click="select(pane)">{{pane.title}}</a>
</li>
</ul>
<div class="tab-content" ng-transclude></div>
</div>
my-pane.html
<div class="tab-pane" ng-show="selected">
<h4>{{title}}</h4>
<div ng-transclude></div>
</div>
指令
myPane
有一個require
選項,值時^^myTabs
,當指令使用這個選項時,$ compile
會拋出錯誤,除非找到了聲明的控制器.^^
前綴表示指令會在它的父元素上尋找指定的控制器.(一個^
前綴表示會在元素自身或父元素上尋找指定控制器;沒有前綴的話表明指令只會在元素自身尋找)
因此這個myTabs
控制器是從哪兒來的呢?指令能夠使用controller
選項來聲明控制器,這毫不意外.就像你看到的這樣,myTabs
指令使用了這個選項.就像ngController
一樣.這個選項給指令的模板綁定了一個控制器.
如果你需要在這個模板之外調(diào)用控制器或者是任何綁定到這個控制器上的函數(shù),你能夠使用選項controllerAs
來給這個控制器定義一個別名.這時指令需要定義一個作用域使得這個配置生效.這在指令被作為一個組件來使用時是特別有效的
讓我們回頭看看myPane
的定義,注意link函數(shù)的最后一個參數(shù): tabsCtrl
,當一個指令引入了一個控制器時,link函數(shù)接受這個控制器作為第四個參數(shù).得益于它,myPane
能夠調(diào)用myTabs
的addPane
函數(shù)
如果需要引入多個控制器,指令的require
選項能夠接收數(shù)組參數(shù).相應(yīng)的,link
函數(shù)同樣接收數(shù)組作為參數(shù).
angular.module('docsTabsExample', [])
.directive('myPane', function() {
return {
require: ['^^myTabs', 'ngModel'],
restrict: 'E',
transclude: true,
scope: {
title: '@'
},
link: function(scope, element, attrs, controllers) {
var tabsCtrl = controllers[0],
modelCtrl = controllers[1];
tabsCtrl.addPane(scope);
},
templateUrl: 'my-pane.html'
};
});
聰明的讀者可能會思考link
和controller
之間的區(qū)別.基本的區(qū)別是controller
能夠暴露出API,link
函數(shù)可以通過使用require
來引入控制器
最佳實踐: 當你想要向其他指令暴露出API時使用
controller
,其他情況下使用link
總結(jié)##
這里我們已經(jīng)見識了指令的常見使用場景.這里的每一個例子對于你創(chuàng)建自定義指令都是一個好的開始.
你也許會對指令編譯過程深層次的解釋很感興趣.這些東西會在下一篇編譯指南中講到.
寫在后面
- 首先是起因,使用angular進行項目開發(fā)大半年了,這兩天要帶新入職的妹子,被問到一些問題,發(fā)現(xiàn)有些東西自己都記得不是很清楚了,以前也沒做過系統(tǒng)性的總結(jié),于是打算重新看一遍官方文檔,于是就有了這篇官網(wǎng)上開發(fā)指南的翻譯
- 指令在angular中無疑非常重要,于是第一篇先從自定義指令開始吧,后續(xù)應(yīng)該會再挑幾篇進行翻譯.
- 自己水平,文筆有限,翻譯難免有錯誤疏漏之處,不吝指正,
- 原文在此, AngularJS開發(fā)者指南之自定義指令
- 文中所有實例代碼有功夫的話后續(xù)會上傳到github