本文執行個體講述了AngularJS封裝指令方法。分享給大家供大家參考,具體如下:
引言:angularjs是一個中等重量級的前端開發架構
HTML是一門很好的為靜態文本設計的語言,但要構建動態web應用它就顯的乏力了。通常,我們使用以下技術來解決靜態網頁技術在構建Live App上的不足:
1.類庫:類庫是一類函數的集合,它能協助你寫web應用。這裡起主導作用是你的代碼,由你來決定何時使用類庫。典型的類庫,例如prototype、jQuery等。
2.架構:架構式一種特殊的、已經實現的web應用,你只需要填充具體的商務邏輯。這裡架構是起主導作用的,由它根據具體的邏輯來調用你的代碼。典型的架構例如knockout,sproutcore, YUI等。AngularJS也是其中之一。
架構又有輕重之分。我對輕重的判斷標準是,是否需要很多的第三方類庫來協助你實現功能。顯然,backbone這種屬於輕量級架構,它簡單易用,專註於前端Mvc的實現,故而你還需要很多第三方類庫(至少jquery)來完成dom操作、UI等各種各樣的內容。Yui、dojo屬於重型架構,他們的作者企圖搞出一個森羅永珍的架構+組件庫,包括代碼動態調用、各種UI組件都包含在內,學習成本較高,但是一旦精通,至少這個項目別無所求。從這個角度講,輕量級架構好比毛坯房,還需要各種工具做裝修,但是對於開發人員來說也更靈活。重量級架構好比精裝修的房間,你只需要的是適應它,但如果要自己做出大刀闊斧的修改,那就稍微有點傷經動骨了。
angularjs,在我看來是介於以上兩類之間,是個中等重量級的架構。即不像backbone那麼簡單,也不像dojo和Yui那麼包羅永珍。很多時候,妄圖包羅永珍,往往會出現很多子模組的品質高不成低不就,並且修改起來較為困難。過分精簡,則架構內容單薄需要寫的內容太多。angularjs這種相對中庸的風格,則非常符合我的需求。目前,AngularJS三個我認為最為精妙的組件就是資料繫結(Scope),指令(Directive)和依賴注入(Dependency Injection),表現得非常好。相對而言,它的UI組件和動畫則是弱項。可以說,選擇了angularjs,就意味著選擇了jquery式的組件庫方式來彌補它的不足,要完成一個web應用必須跟第三方類庫打交道。
現在已經有許多針對angularjs寫的UI外掛程式,有的是結合了bootstrap,有的是結合了jquery, 雖然不太完善,都很值得參考:http://angular-ui.github.io/
與jquery類庫的協作
第三方類庫中,不得不提的是大名鼎鼎的jquery,現在基本上已經是國內web開發的必修工具了。它靈活的dom操作,讓很多web開發人員欲罷不能。再加上已經很成熟的jquery UI 庫和大量jquery 外掛程式,幾乎是一個取之不盡用之不竭的寶庫。然而,它是否能與angularjs結合呢?
很多angularjs原教旨主義者對此持否定態度。他們認為,既然已經使用了angularjs做web應用程式框架,那就必須避免其他類庫的幹擾,做純淨的MvvM模式應用。任何類似jquery的dom操作,都是不潔的。把所有和介面相關的, 比如dom操作, 都放在directive中, 這樣頁面中directive而沒有代碼,跟JSF思想一致。MVVM,DSL,組件化的思想這才是web的趨勢。嗯,想法很好,原教旨主義者想法都是這麼純潔。但事實情況是,使用了angularjs我們就離不開jquery。
眾所周知,angularjs裡面事實上已經內建了jquery lite.,而且angularjs源碼中很多方法直接就是使用jquery方法。例如angularjs的事件綁定機制。既然Crowdsourced Security Testing們都在用,我們又何苦不用?組件化的思想沒有錯,但沒必要因此把自己的手腳綁住。唯一要注意的問題是,不要用jquery的代碼破壞了angularjs的結構。對此我的原則如下,不足之處還請指出:
模組劃分、服務、路由、依賴注入等重要方面上都得使用angularjs的方式,只有某些具體內容(通常是一些Ui)才使用jquery。 避免在controller裡面寫了一堆直接操作dom元素的 jquery代碼。使用angularjs的模板綁定機制。 常用的組件要用angularjs的方法抽取出來,但組件具體實現則不必糾結於是否使用jquery及其外掛程式。 .使用第三方類庫時,在變數和函數命名時有特殊標記(通常是加上這個類庫名的縮寫)。
jquery,更是建議作為angularjs的依賴,先於angularjs載入進來。
事實上,選擇了angularjs這樣的架構中德中等重量級選手,就意味著你必須添加其他類庫。而jquery,更是建議作為angularjs的依賴,先於angularjs載入進來。因為在查看angularjs API的時候,我已經發現,其中許多功能,事實上是依賴於jquery的。典型的例子,就是官網的ng-blur指令。
<input type="text" ng-model="name" ng-blur="checkname()" > ng-blur指令,是在焦點離開某個元素時觸發的指令。對於上例,即在焦點離開該文本輸入框時,觸發checkname()函數。
看起來很簡單,但是你如果真的使用了這個指令,你就會發現它根本不起效果。在仔細查看文檔後,我才發現這實際是Crowdsourced Security Testing們使用jquery的blur方法實現的函數(而且事實上根本沒有真正實現並放在當前的版本裡)。那麼就算我們想寫一個,離開jquery原生庫是不行的,因為blur方法並未封裝到angularjs內帶的jquery lite裡。換句話說,必須先載入完整的jquery才能使用。於是,我乾脆自己寫了一個標籤:
/** angular directive onBlur** @description my ng-blur* @require jquery*/$compileProvider.directive('onBlur', function() { return { restrict : 'A', link : function(scope, elm, attrs) { elm.bind('blur', function() { scope.$apply(attrs.onBlur); }); } };});
這已經很好了。
但是還不夠完美。由於$apply方法接受函數的問題,所以直接像上面這樣寫,有可能導致angularjs運行時報錯:$apply already in progress
避免這個問題的發生,則需要對$apply方法進行加工:
/* factory function safeApply** @description If you find yourself triggering the '$apply already in progress' error while developing with Angular.JS* (for me I find I hit most often when integrating third party plugins that trigger a lot of DOM events),* you can use a 'safeApply' method that checks the current phase before executing your function.** @param scope, the action scope, mostly is the topmost controller* @param fn, the function which you want to apply into scope* @see https://coderwall.com/p/ngisma*/.factory('safeApply', function($rootScope) { return function(scope, fn) { var phase = scope.$root.$$phase; if (phase == '$apply' || phase == '$digest') { if (fn && ( typeof (fn) === 'function')) { fn(); } } else { scope.$apply(fn); } }});
那麼之前的onblur標籤,就應該改為:
/** angular directive onBlur** @description my ng-blur* @require jquery*/$compileProvider.directive('onBlur', function(safeApply) { return { restrict : 'A', link : function(scope, elm, attrs) { elm.bind('blur', function() { safeApply(scope, attrs.onBlur); }); } };});
以上代碼我已經加入了自己的angular_extend模組,在自己的項目中使用了,效果很好。
將jquery 外掛程式用angularjs的方式封裝成組件的例子
icheck是一個jquery外掛程式,用於跨瀏覽器美化Checkbox和Radio按紐。關於它的介紹,在http://www.bootcss.com/p/icheck/
一般來說,它的使用方法是在dom載入後加一段jquery代碼:
$('input').iCheck({ labelHover : false, cursor : true, checkboxClass : 'icheckbox_square-blue', radioClass : 'iradio_square-blue', increaseArea : '20%'});
但是既然要放在我們的項目裡,就不能到處塞這種直接操作dom的jquery代碼,既不美觀,也不易維護。按照之前所說的原則,最好將其封裝成angular指令的模式,放在公用模組裡來調用。這裡我將我建立的指令命名為ng-icheck。如此,我們只要寫在某個checkbox或者radio的html標籤裡加上一句ng-ickeck即可。具體實現如下:
/* * angular directive ng-icheck * * @description icheck is a plugin of jquery for beautifying checkbox & radio, now I complied it with angular directive * @require jquery, icheck * @example <input type="radio" ng-model="paomian" value="kangshifu" ng-icheck> * <input type="checkbox" class="icheckbox" name="mantou" ng-model="mantou" ng-icheck checked> */$compileProvider.directive('ngIcheck', function($compile) { return { restrict : 'A', require : '?ngModel', link : function($scope, $element, $attrs, $ngModel) { if (!$ngModel) { return; } //using iCheck $($element).iCheck({ labelHover : false, cursor : true, checkboxClass : 'icheckbox_square-blue', radioClass : 'iradio_square-blue', increaseArea : '20%' }).on('ifClicked', function(event) { if ($attrs.type == "checkbox") { //checkbox, $ViewValue = true/false/undefined $scope.$apply(function() { $ngModel.$setViewValue(!($ngModel.$modelValue == undefined ? false : $ngModel.$modelValue)); }); } else { // radio, $ViewValue = $attrs.value $scope.$apply(function() { $ngModel.$setViewValue($attrs.value); }); } }); }, };});
在以上代碼中值得注意的是:使用了icheck外掛程式後,會產生一個美化過的div覆蓋在原來的checkbox或者radio之上,而原來的checkbox或者radio會被影藏。故而,當我們點擊它們時,不會直接觸發事件,使得綁定到checkbox或者radio上的model值改變。所以我們這裡需要重新綁定事件,使用
$ngModel.$setViewValue() 方法來給model賦值。具體邏輯,則相根據checkbox和radio而不同。詳見以上代碼。
由於以上代碼寫在我的項目中的通用模組common_angular_component.js裡,故而在調用了該通用模組的頁面裡,直接使用ng-icheck指令即可實現ickeck的美化效果,同時避免了大量重複的jquery代碼的出現。
更多關於AngularJS相關內容感興趣的讀者可查看本站專題:《AngularJS入門與進階教程》及《AngularJS MVC架構總結》
希望本文所述對大家AngularJS程式設計有所協助。