最近一段時間准備使用AngularJs中的自定義Directive重構一下代碼。
在這里說明一下,把自定義控件封裝成Directive並不一定是要復用,而是要讓代碼結構更加清晰。就好像你將一個長方法拆分成多個獨立的小方法,也未必要復用它們一樣。職責獨立等一票好處,會讓后期維護更加輕松。
在重構的過程中,我遇到了這樣一個問題,先上圖:
圖一:
這就是我要重構的界面,由於之前時間緊,將這三個Filter和兩個button都寫在了一個頁面中。當時我已經預感到,如果將這里面的狀態都寫到一個scope上,將出現后期近乎不可維護的災難。盡管我已經將它們用ViewModel隔離,但代碼結構依然讓我很不滿意。
你說啥?沒看出來哪復雜。好,你等着,接下來讓你看看里面啥樣。
圖二:
這是其中一個下拉框展開的模樣。什么?你覺得這很一般吶?沒看出它多么猙獰?好,你等着。上圖三。
圖三:
這是三哥展開之后的完全體。現在你應該明白將它們封裝成directive是多么必要。
(上圖中的這個產品是如今風靡全球的DocAveOnline,使用微軟SharePoint的公司沒有不知道的。啥?你沒聽過?偶,忘了說了,只有大公司才用得上)
我重構的思路很簡單,將每一個下拉框都獨立寫成一個directive,然后將哥仨嵌入同一個Directive中,這個外層的directive里面包含Filter和Reset兩個Button。
這里原本的交互邏輯是這樣的,每個下拉框中的元素都有自己的初始化狀態。但是不論你之前怎么霍霍,點擊那個Reset按鈕之后,要恢復每個下拉框的“出廠設置”。
每個下拉框的Directive擁有獨立的scope是肯定的,一切自給自足,與外界無關。也就是說,每個下拉框的reset方法是封裝在自己的Directive之中。
那么問題來了,我點擊Reset按鈕之后,怎么才能調用其子元素中的reset方法?
開始我嘗試使用require和link的組合,我是想將reset方法封裝到下拉框的controller中,然后在父元素中將三個下拉框對應的directive名字寫到require中,這樣我就能在父元素的link方法的第四個參數中取到,由這三個子元素controller組成的數組,這我就能輕松調用每個controller的reset方法。(本來controller的作用就是對外暴露API)
可寫着寫着我就發現不對,好像require只能找自己或者向父元素查找,沒法由父元素發起向子元素查找的行動。(也許有,但沒找到。如果你知道請在留言中告訴我。)
在江郎才盡的情況下,請教了我師父,他告訴我以下這種方法,其實一點兒都不難,就看你是否理解了自定義directive中,scope里面的,'='這個符號。
稍微懂點兒就知道,這是雙向綁定的意思。那解決辦法來了,代碼如下:
Html部分:
<div ng-controller="myCtrl"> <div> <inner-directive filter-model="filterModel"></inner-directive> <button ng-click="filterModel.reset()">click</button> </div> </div>
Js部分:
<script> angular.module('app',[]) .controller('myCtrl',function($scope){ $scope.filterModel = { name:'xiaomablog' }; }) .directive('innerDirective',function(){ return { restrict:'E', scope:{ filterModel:'=' }, template:'<div ng-bind="filterModel.name"></div>', link:function(scope,elem,attrs){ scope.filterModel.reset = function(){ console.log('reset'); }; } } }); </script>
在innerDirective中的scope屬性中,定義了filterModel這個雙向綁定的屬性,這就意味着directive內部可以霍霍這個屬性,外部myCtrl中也可以霍霍這個屬性,這樣的話彼此都能調用對方霍霍的結果。
我貼心地下了斷點,debug給你看:
第一步:
Angular的啟動過程是先初始化外部的scope,也就是先執行myCtrl中的方法,給filterModel賦了一個對象;
(注意,此刻filterModel的值為undefined)
第二步:
(沒添加reset方法之前,已經看到了之前加的name屬性)
然后才執行directive的link方法,將scope與DOM緊緊地聯系在一起。
第三步:
此時點擊button,已經能調用reset方法了,並在console中打印出'reset'
大家可以嘗試在myCtrl和innerDirective的link方法中加斷點debug一下,親自觀察執行順序,加深印象。
注意:
我們了解錘子的方式 , 不是盯着它看 , 而是拿起來用。
—— 馬丁·海德格爾
聽馬丁的話,趕緊去敲。