Angular Directive 學習
學習目的:為了更好的瞭解 ng directive 的使用方法。
Directive可能是AngularJS中比較複雜的一個東西了。一般我們將其理解成指令。AngularJS自帶了不少預設的指令,比如ng-app,ng-controller這些。可以發現個特點,AngularJS自帶的指令都是由ng-打頭的。
那麼,Directive究竟是個怎麼樣的一個東西呢?我個人的理解是這樣的:將一段html、js封裝在一起,形成一個可複用的獨立個體,具體特定的功能。下面我們來詳細解讀一下Directive的一般性用法。
AnguarJS directive的常用定義格式以及引數說明
看下面的程式碼:
var myDirective = angular.module('directives', []);
myDirective.directive('directiveName', function($inject) {
return {
template: '<div></div>',
replace: false,
transclude: true,
restrict: 'E',
scope: {},
controller: function($scope, $element) {
},
complie: function(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
},
post: function postLink(scope, iElement, iAttrs, controller) {
}
};
},
link: function(scope, iElement, iAttrs) {
}
};
});
複製程式碼
- 這裡直接return了一個object,物件中包括比較多的屬性,這些屬性都是對自定義directive的定義。詳細的含義,下面會繼續說明。
- return物件引數說明
return {
name: '',
priority: 0,
terminal: true,
scope: {},
controller: fn,
require: fn,
restrict: '',
template: '',
templateUrl: '',
replace: '',
transclude: true,
compile: fn,
link: fn
}
複製程式碼
如上所示,return的物件中會有很多的屬性,這行屬性都是用來定義directive的。
下面我們來一個個的說明他們的作用。
-
name
- 表示當前scope的名稱,一般宣告時使用預設值,不用手動設定此屬性。
-
priority
- 優先順序。當有多個directive定義在同一個DOM元素上時,有時需要明確他們的執行順序。這個屬性用於在directive的compile function呼叫之前進行排序。如果優先順序相同,則執行順序是不確定的(根據經驗,優先順序高的先執行,相同優先順序時按照先繫結後執行)。
-
teminal
- 最後一組。如果設定為true,則表示當前的priority將會成為最後一組執行的directive,即比此directive的priority更低的directive將不會執行。同優先順序依然會執行,但是順序不確定。
-
scope
true
- 將為這個directive建立一個新的scope。如果在同一個元素中有多個directive需要新的scope的話,它還是隻會建立一個scope。新的作用域規則不適用於根模版,因為根模版往往會獲得一個新的scope。
{}
- 將建立一個新的、獨立的scope,此scope與一般的scope的區別在於它不是通過原型繼承於父scope的。這對於建立可複用的元件是很有幫助的,可以有效的防止讀取或者修改父級scope的資料。這個獨立的scope會建立一個擁有一組來源於父scope的本地scope屬性hash集合。這些本地scope屬性對於模版建立值的別名很有幫助。本地的定義是對其來源的一組本地scope property的hash對映。
-
controller
- controller建構函式。controller會在pre-linking步驟之前進行初始化,並允許其他directive通過指定名稱的require進行共享。這將允許directive之間相互溝通,增強相互之間的行為。controller預設注入了以下本地物件:
- $scope 與當前元素結合的scope
- $element 當前的元素
- $attrs 當前元素的屬性物件
- $transclude 一個預先繫結到當前scope的轉置linking function
- controller建構函式。controller會在pre-linking步驟之前進行初始化,並允許其他directive通過指定名稱的require進行共享。這將允許directive之間相互溝通,增強相互之間的行為。controller預設注入了以下本地物件:
-
require
- 請求另外的controller,傳入當前directive的linking function中。require需要傳入一個directive controller的名稱。如果找不到這個名稱對應的controller,那麼將會丟擲一個error。名稱可以加入以下字首:
- ? 不要丟擲異常。這將使得這個依賴變為一個可選項
- ^ 允許查詢父元素的controller
- 請求另外的controller,傳入當前directive的linking function中。require需要傳入一個directive controller的名稱。如果找不到這個名稱對應的controller,那麼將會丟擲一個error。名稱可以加入以下字首:
-
restrict
- EACM的子集的字串,它限制了directive為指定的宣告方式。如果省略的話,directive將僅僅允許通過屬性宣告
- E 元素名稱:
- A 屬性名:
- C class名:
- M 註釋:
- EACM的子集的字串,它限制了directive為指定的宣告方式。如果省略的話,directive將僅僅允許通過屬性宣告
-
template
- 如果replace為true,則將模版內容替換當前的html元素,並將原來元素的屬性、class一併轉移;如果replace為false,則將模版元素當作當前元素的子元素處理。
-
templateUrl
- 與template基本一致,但模版通過指定的url進行載入。因為模版載入是非同步的,所有compilation、linking都會暫停,等待載入完畢後再執行。
-
replace
- 如果設定為true,那麼模版將會替換當前元素,而不是作為子元素新增到當前元素中。(為true時,模版必須有一個根節點)
-
transclude
- 編譯元素的內容,使它能夠被directive使用。需要在模版中配合ngTransclude使用。transclusion的有點是linking function能夠得到一個預先與當前scope繫結的transclusion function。一般地,建立一個widget,建立獨立scope,transclusion不是子級的,而是獨立scope的兄弟級。這將使得widget擁有私有的狀態,transclusion會被繫結到父級scope中。(上面那段話沒看懂。但實際實驗中,如果通過呼叫myDirective,而transclude設定為true或者字串且template中包含的時候,將會將的編譯結果插入到sometag的內容中。如果any的內容沒有被標籤包裹,那麼結果sometag中將會多了一個span。如果本來有其他東西包裹的話,將維持原狀。但如果transclude設定為’element’的話,any的整體內容會出現在sometag中,且被p包裹)
true/false
轉換這個directive的內容。(這個感覺上,是直接將內容編譯後搬入指定地方)‘element’
轉換整個元素,包括其他優先順序較低的directive。(像將整體內容編譯後,當作一個整體(外面再包裹p),插入到指定地方)
- 編譯元素的內容,使它能夠被directive使用。需要在模版中配合ngTransclude使用。transclusion的有點是linking function能夠得到一個預先與當前scope繫結的transclusion function。一般地,建立一個widget,建立獨立scope,transclusion不是子級的,而是獨立scope的兄弟級。這將使得widget擁有私有的狀態,transclusion會被繫結到父級scope中。(上面那段話沒看懂。但實際實驗中,如果通過呼叫myDirective,而transclude設定為true或者字串且template中包含的時候,將會將的編譯結果插入到sometag的內容中。如果any的內容沒有被標籤包裹,那麼結果sometag中將會多了一個span。如果本來有其他東西包裹的話,將維持原狀。但如果transclude設定為’element’的話,any的整體內容會出現在sometag中,且被p包裹)
-
compile
- 這裡是compile function,將在下面例項詳細說明
-
link
- 這裡是link function ,將在下面例項詳細講解。這個屬性僅僅是在compile屬性沒有定義的情況下使用。
關於scope
這裡關於directive的scope為一個object時,有更多的內容非常有必要說明一下。看下面的程式碼:
scope: {
name: '=',
age: '=',
sex: '@',
say: '&'
}
複製程式碼
這個scope中關於各種屬性的配置出現了一些奇怪的字首符號,有=,@,&,那麼這些符號具體的含義是什麼呢?再看下面的程式碼:
- html
<div my-directive name="myName" age="myAge" sex="male" say="say()"></div>
複製程式碼
- javascript
function Controller($scope) {
$scope.name = 'Pajjket';
$scope.age = 99;
$scope.sex = '我是男的';
$scope.say = function() {
alert('Hello,我是彈出訊息');
};
}
複製程式碼
可以看出,幾種修飾字首符的大概含義:
=
: 指令中的屬性取值為Controller中對應$scope上屬性的取值@
: 指令中的取值為html中的字面量/直接量&
: 指令中的取值為Controller中對應$scope上的屬性,但是這個屬性必須為一個函式回撥 下面是更加官方的解釋:=
或者=expression/attr
在本地scope屬性與parent scope屬性之間設定雙向的繫結。如果沒有指定attr名稱,那麼本地名稱將與屬性名稱一致。
-
例如: 中,widget定義的scope為:{localModel: '=myAttr'},那麼widget scope property中的localName將會對映父scope的parentModel。如果parentModel發生任何改變,localModel也會發生改變,反之亦然。即雙向繫結。
-
@或者@attr 建立一個local scope property到DOM屬性的繫結。因為屬性值總是String型別,所以這個值總返回一個字串。如果沒有通過@attr指定屬性名稱,那麼本地名稱將與DOM屬性的名稱一致。 例如: ,widget的scope定義為:{localName: '@myAttr'}。那麼,widget scope property的localName會對映出"hello "轉換後的真實值。當name屬性值發生改變後,widget scope的localName屬性也會相應的改變(僅僅是單向,與上面的=不同)。那麼屬性是在父scope讀取的(不是從元件的scope讀取的)
-
&或者&attr 提供一個在父scope上下文中執行一個表示式的途徑。如果沒有指定attr的名稱,那麼local name將與屬性名一致。
- 例如:
<widget my-attr="count = count + value">
,widget的scope定義為:{localFn:’increment()’},那麼isolate scope property localFn會指向一個包裹著increment()表示式的function。
一般來說,我們希望通過一個表示式,將資料從isolate scope傳到parent scope中。這可以通過傳送一個本地變數鍵值的對映到表示式的wrapper函式中來完成。例如,如果表示式是increment(amount),那麼我們可以通過localFn({amount:22})的方式呼叫localFn以指定amount的值。
directive 例項講解
下面的示例都圍繞著上面所作的引數說明而展開的。
- directive宣告例項
// 自定義directive
var myDirective = angular.modeule('directives', []);
myDirective.directive('myTest', function() {
return {
restrict: 'EMAC',
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div><h4>Weather for {{ngModel}}</h4</div>'
};
});
// 定義controller
var myControllers = angular.module('controllers', []);
myControllers.controller('testController', [
'$scope',
function($scope) {
$scope.name = 'this is directive1';
}
]);
var app = angular.module('testApp', [
'directives',
'controllers'
]);
<body ng-app="testApp">
<div ng-controller="testController">
<input type="text" ng-model="city" placeholder="Enter a city" />
<my-test ng-model="city" ></my-test>
<span my-test="exp" ng-model="city"></span>
<span ng-model="city"></span>
</div>
</body>
複製程式碼
template與templateUrl的區別和聯絡
templateUrl其實根template功能是一樣的,只不過templateUrl載入一個html檔案,上例中,我們也能發現問題,template後面根的是html的標籤,如果標籤很多呢,那就比較不爽了。可以將上例中的,template改一下。
myDirective.directive('myTest', function() {
return {
restrict: 'EMAC',
require: '^ngModel',
scope: {
ngModel: '='
},
templateUrl:'../partials/tem1.html' //tem1.html中的內容就是上例中template的內容。
}
});
複製程式碼
scope重定義
//directives.js中定義myAttr
myDirectives.directive('myAttr', function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
template: 'Name: {{customerInfo.name}} Address: {{customerInfo.address}}<br>' +
'Name: {{vojta.name}} Address: {{vojta.address}}'
};
});
//controller.js中定義attrtest
myControllers.controller('attrtest',['$scope',
function($scope) {
$scope.naomi = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
$scope.vojta = {
name: 'Vojta',
address: '3456 Somewhere Else'
};
}
]);
// html中
<body ng-app="testApp">
<div ng-controller="attrtest">
<my-attr info="naomi"></my-attr>
</div>
</body>
複製程式碼
其執行結果如下:
Name: Naomi Address: 1600 Amphitheatre //有值,因為customerInfo定義過的
Name: Address: //沒值 ,因為scope重定義後,vojta是沒有定義的
複製程式碼
我們將上面的directive簡單的改一下,
myDirectives.directive('myAttr', function() {
return {
restrict: 'E',
template: 'Name: {{customerInfo.name}} Address: {{customerInfo.address}}<br>' +
'Name: {{vojta.name}} Address: {{vojta.address}}'
};
});
複製程式碼
- 執行結果如下:
Name: Address:
Name: Vojta Address: 3456 Somewhere Else
複製程式碼
因為此時的directive沒有定義獨立的scope,customerInfo是undefined,所以結果正好與上面相反。
transclude的使用
- transclude的用法,有點像jquery裡面的$().html()功能
myDirective.directive('myEvent', function() {
return {
restrict: 'E',
transclude: true,
scope: {
'close': '$onClick' //根html中的on-click="hideDialog()"有關聯關係
},
templateUrl: '../partials/event_part.html'
};
});
myController.controller('eventTest', [
'$scope',
'$timeout',
function($scope, $timeout) {
$scope.name = 'Tobias';
$scope.hideDialog = function() {
$scope.dialogIsHidden = true;
$timeout(function() {
$scope.dialogIsHidden = false;
}, 2000);
};
}
]);
複製程式碼
<body ng-app="phonecatApp">
<div ng-controller="eventtest">
<my-event ng-hide="dialogIsHidden" on-click="hideDialog()">
Check out the contents, {{name}}!
</my-event>
</div>
</body>
<!--event_part.html -->
<div>
<a href ng-click="close()">×</a>
<div ng-transclude></div>
</div>
複製程式碼
- 說明:這段html最終的結構應該如下所示:
<body ng-app="phonecatApp">
<div ng-controller="eventtest">
<div ng-hide="dialogIsHidden" on-click="hideDialog()">
<span>Check out the contents, {{name}}!</span>
</div>
</div>
</body>
複製程式碼
- 將原來的html元素中的元素Check out the contents, !插入到模版的中,還會另外附加一個標籤。
controller
,link
,compile
之間的關係
myDirective.directive('exampleDirective', function() {
return {
restrict: 'E',
template: '<p>Hello {{number}}!</p>',
controller: function($scope, $element){
$scope.number = $scope.number + "22222 ";
},
link: function(scope, el, attr) {
scope.number = scope.number + "33333 ";
},
compile: function(element, attributes) {
return {
pre: function preLink(scope, element, attributes) {
scope.number = scope.number + "44444 ";
},
post: function postLink(scope, element, attributes) {
scope.number = scope.number + "55555 ";
}
};
}
}
});
//controller.js新增
myController.controller('directive2',[
'$scope',
function($scope) {
$scope.number = '1111 ';
}
]);
//html
<body ng-app="testApp">
<div ng-controller="directive2">
<example-directive></example-directive>
</div>
</body>
複製程式碼
- 上面小例子的執行結果如下:
Hello 1111 22222 44444 5555 !
複製程式碼
由結果可以看出來,controller先執行,compile後執行,link不執行。 我們現在將compile屬性註釋掉後,得到的執行結果如下:
Hello 1111 22222 33333
!