前言:
????從移動端剛入門直接轉到前端angularJs快一年了,感覺還是在入門階段,只會使用一些簡單的東西,所以平常有時間會按照入門的思路去補習一下angularjs的基礎部分,然后整理出來。這一篇的指令基礎,也整理了好兩天,借鑒了很多網上的文章,自己慢慢地寫了一些小demo,基礎部分的東西理解是理解了,不過在實際工作中使用的話還是需要再多研究研究。整理出來分享給大家,讓像我一樣入門的新手也可以有個全面的了解。
概述
????angularJS中呢,有很多印象深刻的且方便的內容,其中呢,數據雙向綁定和指令算是比較有特色的兩個內容了,我們這呢就講講指令的學習。
????指令,就是用新屬性,來擴展HTML,讓DOM元素有用特定的行為。就我個人對指令的理解,就是把一些復雜,復用性多的代碼或者代碼量大的插件之類的封裝成一個指令,在前端html頁面上使用,這樣既簡化了頁面,優化了代碼,也增加了工作效率。
????angularJS自己有很多內置指令,一般前綴都是ng-,比如說ng-model,ng-app,ng-show,ng-repeat等等。但這些也沒什么好說的,因為這些基本上都是angular已經封裝好的,直接使用就可以了。AngularJS內置的指令外,我們還可以創建自定義指令。
自定義指令
指令的屬性
????首先先簡略的看下自定義的一些基本上屬性和作用
屬性 | 作用 |
---|---|
restrict | 申明標識符在模版中作為元素,屬性,類,注釋或組合,如何使用 |
priority | 設置模版中相對于其他標識符的執行順序 |
Template | 指定一個字符串式的內嵌模版,如果你指定了模版是一個URL,那么是不會使用的 |
tempateUrl | 指定URL加載的模版,如果你已經指定了內嵌的模版字符串,那么它不會使用的 |
Replace | 如果為真,替換當前元素,如果是假或未指定,拼接到當前元素 |
Transclude | 移動一個標識符的原始字節帶你到一個新模版的位置 |
Scope | 為這個標識符創建一個新的作用域,而不是繼承父作用域 |
Controller | 創建一個控制器通過標識符公開通信API |
Require | 當前標識符需要另外一個標識符提供正確的函數功能 |
Link | 通過代碼修改目標DOM元素的實例,添加事件監聽,建立數據綁定 |
Compile | 通過標識符拷貝編程修改DOM模版 |
指令基礎屬性
????接下來我們簡單的說明下各個屬性的具體情況。
1.restrict(string)
????簡單的一句話來解釋這個屬性是什么意思,就是決定你所寫的自定義指令可以以哪幾種方式出現在你的代碼中。
????這個屬性的值有四個,也就是代表可以以四種不同的方式來使用。
值 | 樣式 | 概述 | 示例 |
---|---|---|---|
E | Element | 作為一個新的HTML元素來使用。 | <hello></hello> |
A | Attribute | 作為一個元素的屬性來使用 | <div hello></div> |
C | Class | 作為一個元素的類來使用 | <div class="hello"></div> |
M | Comment | 作為注釋來使用 | <!--directive: hello --> |
????一般情況,我們常用的是EA,在第四個注釋中使用的時候,冒號后面得加一個空格,不然沒法用,不過這種注釋形式我們用的也比較少。一般情況下,我們在寫指令的時候可以不寫restrict屬性,那樣就會默認是A。
2.priority(number)
????這個屬性是來規定自定義的指令的優先級的,意思就是一個DOM元素上有多個指令的時候,有限處理哪個就看這個值的設置。優先級高的就先執行。默認是0.一般情況都不需要去設置。
3.templates(string or function)/tempateUrl
????規定了指令被Angular編譯和鏈接(link)后生成的HTML標記,可以很簡單,也可以很復雜,當值是一個方法的時候,方法返回的就是代表模板的字符串,同事也可以在里面使用{{}}表達式。
template: function () {
return '<div>你好</div>';
}
????不過,通常情況下呢,template這個屬性都會被templateUrl取代掉,用它來指向一個外部的文件地址,所以我們通常把模板放在外部的一個HTML文件中,然后使用templateUrl來指向他。因為我們想要封裝成指令的那一塊html有時候代碼量會很大,或者是比較復雜,那么直接寫在這個屬性下面需要拆分,會很麻煩,其實可以單獨寫一個文件,然后在這個屬性下面寫上地址指向這個文件。
????同時templateUrl用于指定將被加載的服務器文件,我們可以預緩存這些模版,減少get請求數,提高性能
4.Replace(boolean)
????這個屬性用來規定生成的HTML內容是否會替換掉定義此指令的HTML元素。我們簡單的寫一個指令
var app = angular.module('testapp', []);
app.directive('hello',function(){
return {
restrict : 'E',
replace : true,
template : '<div>hello angular</div>'
};
});
????使用
<body ng-app="testapp">
<hello></hello>
</body>
????這個時候,我們的replace設置的是true,
????當我們值設置為false時
兩者的差別在于,指令部分會不會被模板中的內容所替換。
5.Transclude(boolean)
????這個屬性用來讓我們規定指令是否可以包含任意內容,默認為false,表示不開啟,如果設置為true,則開啟該屬性,當開啟后,則可以在模板中通過ng-transclude方式替換指令元素中的內容。
????舉個例子來看
<body ng-app="testapp">
<hello>
哎呦我去
</hello>
</body>
<script>
var app = angular.module('testapp', []);
app.directive('hello',function(){
return {
restrict : 'E',
replace : true,
transclude: true,
template : '<div ng-transclude>hello angular</div>'
};
});
</script>
????頁面是這么顯示的:
????源碼里面則變成這樣:
????如果值為false,那么頁面會變成空白的。在我個人看來,這個屬性的設置,就是要把指令里面的內容替換掉,移動原始的內容到新模版中,當設置成為true時,標識符會刪除原來的內容,并通過ng-transclude標識符使它重新插入到模版中,也可以這么用
<body ng-app="testapp">
<hello>
哎呦我去
</hello>
</body>
<script>
var app = angular.module('testapp', []);
app.directive('hello',function(){
return {
restrict : 'E',
replace : true,
transclude: true,
template : '<div>hello <span ng-transclude></span></div>'
};
});
</script>
????那么頁面會顯示 hello 哎呦我去。不過這個屬性我們平常寫簡單的指令的時候也不怎么用得著。
指令的編譯和鏈接函數
????上面說了一些基本的屬性,基本屬性在理解上面相對比較簡單,設置值也不復雜,但編譯和鏈接函數涉及到的東西就比較多了。我們在基礎屬性中也只是把一些模板或者html替換成指令,但是實際上的操作都是在編譯或者鏈接功能里面。這兩個功能是angular引用和創建實時視圖的后面步驟。
????angular初始化過程是這樣的。
流程 | 內容 |
---|---|
腳本加載 | 加載angular,查找ng-app標識符找到應用綁定 |
編譯階段 | 在這一階段,angular遍歷DOM標志模版中所有注冊的標志,對于每個標識符,基于標識符規則(template,replace,transclude等等)改造DOM,然后如果編譯函數存在就調用它,結果一個編譯的template函數,它會調用所有的標識符搜集的link韓素 |
鏈接階段 | 為了讓視圖動起來,angular為每個標識符運行link函數,link函數通常在DOM或模型上創建監聽器,這些監聽器讓視圖和模型始終保持一致 |
????因此到了編譯階段,它處理轉換了模版,鏈接階段,它處理了修改視圖中的數據,沿著這些思路,標識符中表一功能和鏈接功能主要區別就是鏈接功能轉換了模版自身,而連接功能在模型和視圖上創建了動態鏈接,就是在第二階段,作用域scpoes被附加到了編譯過程的link功能上,通過數據綁定,標識符變活了
1.Scope(boolean or object)
????該屬性是用來定義指令的scope的范圍,默認情況下是false,也就是說繼承了父控制器的scope,可以隨意使用父控制器中的scope里的屬性,但是有時候會污染到父scope的屬性,所以得另外設置。
????獲取作用域scope的三種選擇
- 標識符DOM元素中已經存在的作用域
- 創建一個繼承封閉的控制器作用域的新作用域,以便讀取結構樹作用域的所有值。
- 獨立作用域,從父類中不繼承任何屬性,當你需要隔離這個標識符的操作和父類作用域時,創建可從用的組建來使用這個選項
三種方式 | 設置 |
---|---|
已有作用域 | scope:false(如果沒有指定,這就是默認值) |
新作用域 | scope:true |
獨立作用域 | scope:{屬性名次和綁定風格} |
????舉個例子吧:首先是scope的值不設置的時候,默認是false,和設置成true的時候,是都可以獲取到父作用域的屬性的。
<body ng-app="testapp" ng-controller="myCtrl">
<hello></hello>
</body>
<script>
var app = angular.module('testapp', []);
app.controller('myCtrl',['$scope', function ($scope) {
$scope.abc = '+abc是父作用域的屬性';
}]);
app.directive('hello',function(){
return {
restrict : 'E',
replace : true,
template : '<div>hello {{abc}}</div>',
scope:false,
link: function (scope) {
console.log(scope.abc)
}
};
});
</script>
????顯示結果都是
????但是如果把scope的值設置成{},表示創建一個隔離的scope,不會繼承父scope的屬性。那么顯示的結果只有hello。但是在有的時候我們也要需要訪問父scope里的屬性或者方法,我們可以通過標識符屬性的鍵值對父類傳遞指定的屬性給獨立作用域
符號 | 意義 |
---|---|
@ | 傳遞字符串屬性,你可以通過使用改寫{{}}屬性值從作用域中進行數據綁定(單向綁定) |
= | 數據綁定屬性在標識符父作用域的屬性中 |
& | 傳遞一個來自父作用域的函數,稍后調用 |
- @:如果父作用域的屬性內容修改了,子作用域對應的屬性內容也會隨之修改,而如果子作用域屬性內容修改了,是不會影響父作用域對應的屬性內容的。
????舉個例子
<body ng-app="testapp" ng-controller="myCtrl">
<input ng-model="name">
<hello name="{{name}}"></hello>
</body>
<script>
var app = angular.module('testapp', []);
app.controller('myCtrl',['$scope', function ($scope) {
}]);
app.directive('hello',function(){
return {
restrict : 'E',
replace : true,
template : '<div>hello {{name}}</div>',
scope:{
name:'@'
}
};
});
</script>
????那么結果顯示是
- =:創建一個父作用域與子作用域可以同時共享的屬性,即父作用域修改了該屬性,子作用域也隨之改變,反之亦然。
????舉個例子
<body ng-app="testapp" ng-controller="myCtrl">
<input type="text" ng-model="name" placeholder="Enter a name"><br>
{{name}}<br>
<hello name="name"></hello>
</body>
<script>
var app = angular.module('testapp', []);
app.controller('myCtrl',['$scope', function ($scope) {}]);
app.directive('hello',function(){
return {
restrict : 'E',
replace : true,
template : '<input type="text" ng-model="name">',
scope:{
name:'='
}
};
});
</script>
????那么結果顯示是
- &:可以在獨立的子作用域中直接調用父作用域的方法,在調用時可以向函數傳遞參數。
????舉個例子
<body ng-app="testapp" ng-controller="myCtrl">
<input type="text" ng-model="name" placeholder="Eneter a color">
{{name}}
<hello saysomething999="say();" name="hahahahhahah"></hello>
</body>
<script>
var app = angular.module('testapp', []);
app.controller('myCtrl',['$scope', function ($scope) {
$scope.say = function () {
alert('hello');
};
$scope.name = 'leifeng';
}]);
app.directive('hello',function(){
return {
restrict : 'E',
replace : true,
scope:{
name:'@'
},
template : '<button type="button" ng-bind="name" ng-init="saysomething();"></button>'
};
});
/script>
????那么結果顯示是
2.link(string or function)和compile(String or Array)
????在angularJs應用啟動之前,它們是以HTML文本形式存在文本編輯器當中。應用啟動會進行編譯和鏈接,作用域會同HTML進行綁定。這個過程包含了兩個階段!
????在編譯的階段,angularJs會遍歷整個的文檔并根據JavaScript中指令定義來處理頁面上什么的指令。在遍歷的過程中,有可能一層套著一層,一直延深處遍歷。一但遍歷和編譯完畢就會返回一個叫做模板函數的函數。在這個函數沒被返回(return)之前我們可以對編譯后的DOM樹進行修改。通常情況下,如果設置了compile函數,說明我們希望在指令和實時數據被放到DOM中之前進行DOM操作,在這個函數中進行諸如添加和刪除節點等DOM操作是安全的。本質上,當我們設置了link選項,實際上是創建了一個postLink() 鏈接函數,以便compile() 函數可以定義鏈接函數。編譯函數(compile)負責對模板DOM進行轉換。鏈接函數(link)負責將作用域和DOM進行鏈接。
????compile和link區別在于
- compile函數的作用是對指令的模板進行轉換;
- link作用是在模型和視圖之間建立關聯,包括在元素上注冊事件監聽;
- scope在鏈接階段才會被綁定到元素上,因此compile階段操作scope會報錯;
- 對于同一個指令的多個實例,compile只會執行一次;而link對于指令的每個實例都執行一次;
- 一般情況下只需要寫link函數就夠了;
- 如果編寫自定義的compile函數,自定的link函數無效,因為compile函數應該返回一個link函數供后續處理
????簡單的總結上面的話,就是一般情況我們不咋用compile,要注意 compile 函數不能訪問 scope,并且必須返回一個 link 函數。但是如果沒有設置 compile 函數,你可以正常地配置 link 函數,(有了compile,就不能用link,link函數由compile返回)。
????compile函數可以寫成如下的形式:
app.directive('test', function() {
return {
compile: function(tElem,attrs) {
//do optional DOM transformation here
return function(scope,elem,attrs) {
//linking function here
};
}
};
});
????指令生成的模板,在沒有邏輯支持下,只是顯示出來你想要的靜態效果而已,沒有實際作用,默認情況下,指令并不會創建新的作用域,更多情況是使用父scope。意思就是在指令存在于一個控制器下,就會使用這個控制器的scope,但是在哪使用呢,這就需要link函數了。
????link函數中,有三個參數:
- scope:指令的scope,默認是父控制器的scope。
- elem:指令元素
- attrs:一個包含了指令所在元素的標準化的參數對象。
????link函數主要用來為DOM元素添加事件監聽、監視模型屬性變化、以及更新DOM。舉個簡單的例子來看吧。
<body ng-app="testapp" ng-controller="myCtrl">
<hello></hello>
</body>
<script>
var app = angular.module('testapp', []);
app.controller('myCtrl',['$scope', function ($scope) {}]);
app.directive('hello',function(){
return {
restrict : 'E',
replace : true,
template : '<div id="hello" ng-mousemove="move()" ng-mouseleave="leave()">hello world</div>',
link : function(scope,element,attrs) {
scope.move = function () {
document.getElementById('hello').style.color = "red";
console.log("mousemove");
};
scope.leave = function () {
document.getElementById('hello').style.color = "black";
console.log('mouseleave')
}
}
}
});
</script>
????鼠標移入變紅色,移除變黑色,頁面顯示是這樣的(動態圖上看不到鼠標):
3.controller(string or function)和require(String or Array)
????scope是指令與外界作用域通訊的橋梁,而require是指令與指令之間通訊的橋梁。這個參數最大的作用在于,當要開發單指令無法完成,需要一些組合型指令的控件或功能,例如日期控件,通過require參數,指令可以獲得外部其他指令的控制器,從而達到交換數據、事件分發的目的。require的作用是為了讓父子指令或者兄弟指令的controller之間搭建一個橋梁。也就是說父指令里的controller里面的數據能分享給子指令的controller,其中子指令的link第四個參數的值是父指令的controller對象的作用域上下文。
????require有兩個修飾符號:”?”、”^”
- ? : 如果require沒有找到相應的指令避免報錯,還能確保程序的正常執行
- ^ : 表示往父級查找
????controller屬性值是一個構造函數,在創建父元素指令時添加,并可以在函數中創建多個屬性或方法。
????直接拿網上的一個小例子來說明下。
<body ng-app="requireapp" ng-controller="myCtrl1">
<hello>
<div>hello</div>
<beautiful good>
beautiful
</beautiful>
</hello>
</body>
<script>
var app = angular.module('requireapp', []);
app.controller('myCtrl1',['$scope', function ($scope) {}]);
app.directive("hello",function(){
return {
restrict : "E",
controller : function($scope){
$scope.name = "張三";
this.information = {
name : $scope.name,
age : 25,
job : "程序員"
}
},
link : function(scope){}
}
});
app.directive("beautiful",function(){
return {
restrict : "E",
require : "?good",
controller : function(){
this.name = "beautiful";
},
link : function (scope,element,attrs,good) {
console.log(good.name)
}
}
});
app.directive("good",function(){
return {
restrict : "A",
require : "?^hello",
controller : function(){
this.name = "good";
},
link : function (scope,element,attrs,hello) {
console.log(hello.information)
}
}
});
</script>
????頁面顯示:
????控制臺顯示:
????我們簡單的看下,html中首先寫的是hello指令,這個指令只寫了一個對象,里面有三個數據,name,age,job,值分別是張三,25,和程序員。很簡單的指令。然后在hello指令內部,有一個是div,就是頁面顯示hello的div,可以不看,再看下面的調用的beautiful指令,里面又使用了good指令,good的指令也是很簡單的,聲明了在他的作用域內,name屬性的值是good。同時往父級查找hello指令,沒找到也沒事。第三個指令是beautiful,內部也是找good指令,link第四個參數的值是父指令的controller對象的作用域上下文。link方法內部打印good的那么值,由上面我們看到的good指令,可以知道這個值就是good。good指令中打印的是hello指令的對象,打印出來的也就是hello指令里的對象。最后使用的時候就得到圖上的結果。
????感覺上面這一堆廢話說了也沒啥用,總結出來一句話就是good的控制器跟hello的控制器進行通信,beautiful又可以跟同級的good通信。
小結
????以上呢就是整理出來的全部內容,在我看到,比較重要的地方是在作用域那塊,因為我們平常如果只是使用一些簡單的指令的時候,傳參調用數據這些是比較常用的,之前寫一些簡單的指令的時候總是在糾結模板中數據到底怎么去寫,怎么傳入等等。別的像link,compie,controller的使用區別的話,細看看網上的一些文章也是比較好理解的。觸發的順序直接的差別,相互之間的關系我都有提到等等,
????在看完這些東西之后,可以自己試著寫一個簡單的指令,可以參照我之前一篇文章里寫的時間選擇器來自己練習練習,順路還可以優化下我寫的那個粗糙的指令。另外廢話一句,大家要是看到有什么問題的可以評論我們一起討論討論,因為我也是剛整理出來,對他的了解也并不是很全面,大家可以一起來聊聊。借鑒的一些文章就不貼了,百度下一堆,大家自便。