標籤:區別 執行 但我 head element color vda 迴圈 想法
自訂指令學習有段時間了,學了些紙上談兵的東西,還沒有真正的寫個指令出來呢。。。所以,隨著學習的接近尾聲,本篇除了介紹剩餘的幾個參數外,還將動手結合使用各參數,寫個真正能用的指令出來玩玩。
我們在自訂指令(上)中,寫了一個簡單的<say-hello></say-hello>,能夠跟美女打招呼。但是看看人家ng內建的指令,都是這麼用的:ng-model=”m”,ng-repeat=”a in array”,不單單是作為屬性,還可以賦值給它,與範圍中的一個變數綁定好,內容就可以動態變化了。假如我們的sayHello可以這樣用:<say-hello speak=”content”>美女</say-hello>,把要對美女說的話寫在一個變數content中,然後只要在controller中修改content的值,頁面就可以顯示對美女說的不同的話。這樣就靈活多了,不至於見了美女只會說一句hello,然後就沒有然後了。
為了實現這樣的功能,我們需要使用scope參數,下面來介紹一下。
使用scope為指令劃分範圍
顧名思義,scope肯定是跟範圍有關的一個參數,它的作用是描述指令與父範圍的關係,這個父範圍是指什麼呢?想象一下我們使用指令的情境,頁面結構應該是這個樣子:
<div ng-controller="testC"> <say-hello speak="content">美女</say-hello></div>
外層肯定會有一個controller,而在controller的定義中大體是這個樣子:
var app = angular.module(‘MyApp‘, [], function(){console.log(‘here‘)});app.controller(‘testC‘,function($scope){$scope.content = ‘今天天氣真好!‘;});
所謂sayHello的父範圍就是這個名叫testC的控制器所管轄的範圍,指令與父範圍的關係可以有如下取值:
取值 |
說明 |
false |
預設值。使用父範圍作為自己的範圍 |
true |
建立一個範圍,該範圍繼承父範圍 |
javascript對象 |
與父範圍隔離,並指定可以從父範圍訪問的變數 |
乍一看取值為false和true好像沒什麼區別,因為取值為true時會繼承父範圍,即父範圍中的任何變數都可以訪問到,效果跟直接使用父範圍差不多。但細細一想還是有區別的,有了自己的範圍後就可以在裡面定義自己的東西,與跟父範圍混在一起是有本質上的區別。好比是父親的錢你想花多少花多少,可你自己掙的錢父親能花多少就不好說了。你若想看這兩個範圍的區別,可以在link函數中列印出來看看,還記得link函數中可以訪問到scope吧。
最有用的還是取值為第三種,一個對象,可以用索引值來顯式的指明要從父範圍中使用屬性的方式。當scope值為一個對象時,我們便建立了一個與父層隔離的範圍,不過也不是完全隔離,我們可以手工搭一座橋樑,並允許存取某些參數。我們要實現對美女說各種話就得靠這個。使用起來像這樣:
scope: { attributeName1: ‘BINDING_STRATEGY‘, attributeName2: ‘BINDING_STRATEGY‘,...}
鍵為屬性名稱,值為繫結原則。等等!啥叫繫結原則?最討厭冒新名詞卻不解釋的行為!別急,聽我慢慢道來。
先說屬性名稱吧,你是不是認為這個attributeName1就是父範圍中的某個變數名稱?錯!其實這個屬性名稱是指令自己的模板中要使用的一個名稱,並不對應父範圍中的變數。好難懂啊。。。可能這麼說太不負責任了,稍後的例子中我們來說明。再來看繫結原則,它的取值按照如下的規則:
符號 |
說明 |
舉例 |
@ |
傳遞一個字串作為屬性的值. |
str : ‘@string’ |
= |
使用父範圍中的一個屬性,綁定資料到指令的屬性中. |
name : ‘=username’ |
& |
使用父範圍中的一個函數,可以在指令中調用 |
getName : ‘&getUserName’ |
總之就是用符號首碼來說明如何為指令傳值。你肯定迫不及待要看例子了,我們結合例子看一下,小二,上栗子~
舉例說明
我想要實現上面想像的跟美女多說點話的功能,即我們給sayHello指令加一個屬性,通過給屬性賦值來動態改變說話的內容。主要代碼如下:
app.controller(‘testC‘,function($scope){ $scope.content = ‘今天天氣真好!‘; });app.directive(‘sayHello‘,function(){ return { restrict : ‘E‘, template : ‘<div>hello,<b ng-transclude></b>,{-{cont}-}</div>‘, replace : true, transclude : true, scope : { cont : ‘=speak‘ } };});
然後在模板中,我們如下使用指令:
<div ng-controller="testC"> <say-hello speak="content">美女</say-hello></div>
看看運行效果:
美女換句話
執行的流程是這樣的:
① 指令被編譯的時候會掃描到template中的{ {cont} },發現是一個運算式;
② 尋找scope中的規則:通過speak與父範圍綁定,方式是傳遞父範圍中的屬性;
③ speak與父範圍中的content屬性綁定,找到它的值“今天天氣真好!”
④ 將content的值顯示在模板中
這樣我們說話的內容cont就跟父範圍綁定到了一其,如果動態修改父範圍的content的值,頁面上的內容就會跟著改變,正如你點擊“換句話”所看到的一樣。
這個例子也太小兒科了吧!簡單雖簡單,但可以讓我們理解清楚,為了檢驗你是不是真的明白了,可以思考一下如何修改指令定義,能讓sayHello以如下兩種方式使用:
<span say-hello speak="content">美女</span>
<span say-hello="content" >美女</span>
答案我就不說了,簡單的很。下面有更重要的事情要做,我們說好了要寫一個真正能用的東西來著。接下來就結合所學到的東西來寫一個摺疊菜單,即點擊可展開,再點擊一次就收縮回去的菜單,(偷偷告訴你,這個例子其實是從大漠窮秋的書上抄來的~)。
控制器及指令的代碼如下:(為了不讓文章太長,我後面的代碼要摺疊起來了,請自行點開)
app.controller(‘testC‘,function($scope){ $scope.title = ‘簡歷‘; $scope.text = ‘大家好,我是一名前端工程師,我正在研究AngularJs,歡迎大家與我交流,Email:[email protected]‘; }); app.directive(‘expander‘,function(){ return { restrict : ‘E‘, templateUrl : ‘expanderTemp.html‘, replace : true, transclude : true, scope : { mytitle : ‘=etitle‘ }, link : function(scope,element,attris){ scope.showText = false; scope.toggleText = function(){ scope.showText = ! scope.showText; } } }; });View Code
HTML中的代碼如下:
<script id="expanderTemp.html" type="text/ng-template"><div class="mybox"> <div class="mytitle" ng-click="toggleText()"> {{mytitle}} </div> <div ng-transclude ng-show="showText"></div></div></script>
看看運行效果:
還是比較容易看懂的,我只做一點必要的解釋。首先我們定義模板的時候使用了ng的一種定義方式<script type=”text/ng-template” id="expanderTemp.html">,在指令中就可以用templateUrl根據這個id來找到模板。指令中的{-{mytitle}-}運算式由scope參數指定從etitle傳遞,etitle指向了父範圍中的title。為了實現點擊標題能夠展開收縮內容,我們把這部分邏輯放在了link函數中,link函數可以訪問到指令的範圍,我們定義showText屬性來表示內容部分的顯隱,定義toggleText函數來進行控制,然後在模板中綁定好。 如果把showText和toggleText定義在controller中,作為$scope的屬性呢?顯然是不行的,這就是隔離範圍的意義所在,父範圍中的東西除了title之外通通被屏蔽。
上面的例子中,scope參數使用了=號來指定擷取屬性的類型為父範圍的屬性,如果我們想在指令中使用父範圍中的函數,使用&符號即可,是同樣的原理。
以上是本人對scope的理解,另外有一篇文章對Angular範圍的解釋也比較詳細,有興趣可以參考http://www.angularjs.cn/A09C。
使用controller和require進行指令間通訊
使用指令來定義一個ui組件是個不錯的想法,首先使用起來方便,只需要一個標籤或者屬性就可以了,其次是可複用性高,通過controller可以動態控制ui組件的內容,而且擁有雙向繫結的能力。當我們想做的組件稍微複雜一點,就不是一個指令可以搞定的了,就需要指令與指令的協作才可以完成,這就需要進行指令間通訊。
想一下我們進行模組化開發的時候的原理,一個模組暴露(exports)對外的介面,另外一個模組引用(require)它,便可以使用它所提供的服務了。ng的指令間協作也是這個原理,這也正是自訂指令時controller參數和require參數的作用。
controller參數用於定義指令對外提供的介面,它的寫法如下:
controller: function controllerConstructor($scope, $element, $attrs, $transclude)
它是一個構造器函數,將來可以構造出一個執行個體傳給引用它的指令。為什麼叫controller(控制器)呢?其實就是告訴引用它的指令,你可以控制我。至於可以控制那些東西呢,就需要在函數體中進行定義了。先看controller可以使用的參數,範圍、節點、節點的屬性、節點內容的遷移,這些都可以通過依賴注入被傳進來,所以你可以根據需要唯寫要用的參數。關於如何對外暴露介面,我們在下面的例子來說明。
require參數便是用來指明需要依賴的其他指令,它的值是一個字串,就是所依賴的指令的名字,這樣架構就能按照你指定的名字來從對應的指令上面尋找定義好的controller了。不過還稍稍有點特別的地方,為了讓架構尋找的時候更輕鬆些,我們可以在名字前面加個小小的首碼:^,表示從父節點上尋找,使用起來像這樣:require : ‘^directiveName’,如果不加,$compile服務只會從節點本身尋找。另外還可以使用首碼:?,此首碼將告訴$compile服務,如果所需的controller沒找到,不要拋出異常。
所需要瞭解的知識點就這些,接下來是例子時間,依舊是從書上抄來的一個例子,我們要做的是一個手風琴菜單,就是多個摺疊菜單並列在一起,此例子用來展示指令間的通訊再合適不過。
首先我們需要定義外層的一個結構,起名為accordion,代碼如下:
app.directive(‘accordion‘,function(){ return { restrict : ‘E‘, template : ‘<div ng-transclude></div>‘, replace : true, transclude : true, controller :function(){ var expanders = []; this.gotOpended = function(selectedExpander){ angular.forEach(expanders,function(e){ if(selectedExpander != e){ e.showText = false; } }); } this.addExpander = function(e){ expanders.push(e); } } } });View Code
需要解釋的只有controller中的代碼,我們定義了一個摺疊菜單數組expanders,並且通過this關鍵字來對外暴露介面,提供兩個方法。gotOpended接受一個selectExpander參數用來修改數組中對應expander的showText屬性值,從而實現對各個子功能表的顯隱控制。addExpander方法對外提供向expanders數組增加元素的介面,這樣在子功能表的指令中,便可以調用它把自身加入到accordion中。
看一下我們的expander需要做怎樣的修改呢:
app.directive(‘expander‘,function(){ return { restrict : ‘E‘, templateUrl : ‘expanderTemp.html‘, replace : true, transclude : true, require : ‘^?accordion‘, scope : { title : ‘=etitle‘ }, link : function(scope,element,attris,accordionController){ scope.showText = false; accordionController.addExpander(scope); scope.toggleText = function(){ scope.showText = ! scope.showText; accordionController.gotOpended(scope); } } }; });View Code
首先使用require參數引入所需的accordion指令,添加?^首碼表示從父節點尋找並且失敗後不拋出異常。然後便可以在link函數中使用已經注入好的accordionController了,調用addExpander方法將自己的範圍作為參數傳入,以供accordionController訪問其屬性。然後在toggleText方法中,除了要把自己的showText修改以外,還要調用accordionController的gotOpended方法通知父層指令把其他菜單給收縮起來。
指令定義好後,我們就可以使用了,使用起來如下:
<accordion> <expander ng-repeat="expander in expanders" etitle="expander.title">{-{expander.text}-}</expander></accordion>
外層使用了accordion指令,內層使用expander指令,並且在expander上用ng-repeat迴圈輸出子功能表。請注意這裡遍曆的數組expanders可不是accordion中定義的那個expanders,如果你這麼認為了,說明還是對範圍不夠瞭解。此expanders是ng-repeat的值,它是在外層controller中的,所以,在testC中,我們需要添加如下資料:
$scope.expanders = [ {title: ‘簡歷‘, text: ‘大家好,我是一名前端工程師,我正在研究AngularJs,歡迎大家與我交流,Email:[email protected]‘}, {title: ‘我的愛好‘, text: ‘運動類:籃球、足球、乒乓球。 電腦類:前端技術、打DOTA。 其他類:欣賞美女‘}, {title: ‘性格及工作‘, text: ‘追求完美主義的處女座極品男人就是我啦~嚴重的代碼潔癖以及對垃圾代碼的零容忍!希望通過自己的努力進入理想的公司工作。‘} ];View Code
這下就都全乎了,試一下我們的accordion組件是不是可以正常使用了呢:
理解了其中的道理之後,使用起來就可以得心應手了,我也將在以後的實踐中嘗試編寫更加複雜的組件,此小例子就當是拋磚引玉了~
總結
又到了總結時間,到此為止自訂指令的學習就告一段落了,但我相信相關的知識肯定遠遠不止這些,真正要將指令在項目中用好,還需要理解指令與ng的其他機制如何相互作用,還需更加深入的瞭解ng的指令機制等。所以學與用的轉變還需要實踐的檢驗。
撰寫部落格使我的學習進度變的異常緩慢,要加油了!
來源:http://www.cnblogs.com/lvdabao/p/3407424.html
null
走進AngularJs(五)自訂指令----(下)