最近幾個月頻繁的跟AngularJS打交道,對於web應用開發來說Angular真的是一個神奇的框架,但是沒有東西是完美的,在這篇文章裡我會把我的感悟羅列出來,希望可以產生共鳴(前提是你對Angular已經有所瞭解)。
UI的閃爍
Angular的自動資料繫結功能是亮點,然而,他的另一面是:在Angular初始化之前,頁面中可能會給使用者呈現出沒有解析的表示式。當DOM準備就緒,Angular計算並替換相應的值。這樣就會導致出現一個醜陋的閃爍效果。
上述情形就是在Angular教程中渲染示例程式碼的樣子:
1 2 3 4 5 6 7 8 |
<body ng-controller="PhoneListCtrl"> <ul> <li ng-repeat="phone in phones"> {{ phone.name }} <p>{{ phone.snippet }}</p> </li> </ul> </body> |
如果你做的是SPA(Single Page Application),這個問題只會在第一次載入頁面的時候出現,幸運的是,可以很容易杜絕這種情形發生: 放棄{{ }}表示式,改用ng-bind指令
1 2 3 4 5 6 7 8 |
<body ng-controller="PhoneListCtrl"> <ul> <li ng-repeat="phone in phones"> <span ng-bind="phone.name"></span> <p ng-bind="phone.snippet">Optional: visually pleasing placeholder</p> </li> </ul> </body> |
你需要一個tag來包含這個指令,所以我新增了一個<span>給phone name.
那麼初始化的時候會發生什麼呢,這個tag裡的值會顯示(但是你可以選擇設定空值).然後,當Angular初始化並用表示式結果替換tag內部值,注意你不需要在ng-bind內部新增大括號。更簡潔了!如果你需要符合表示式,那就用ng-bind-template吧,
如果用這個指令,為了區分字串字面量和表示式,你需要使用大括號
另外一種方法就是完全隱藏元素,甚至可以隱藏整個應用,直到Angular就緒。
Angular為此還提供了ng-cloak指令,工作原理就是在初始化階段inject了css規則,或者你可以包含這個css 隱藏規則到你自己的stylesheet。Angular就緒後就會移除這個cloak樣式,讓我們的應用(或者元素)立刻渲染。
Angular並不依賴jQuery。事實上,Angular原始碼裡包含了一個內嵌的輕量級的jquery:jqLite. 當Angular檢測到你的頁面裡有jQuery出現,他就會用這個jQuery而不再用jqLite,直接證據就是Angular裡的元素抽象層。比如,在directive中訪問你要應用到的元素。
1 2 3 4 5 6 7 8 9 |
angular.module('jqdependency', []) .directive('failswithoutjquery', function() { return { restrict : 'A', link : function(scope, element, attrs) { element.hide(4000) } } }); |
(演示程式碼: this plunkr )
但是這個元素jqLite還是jQuery元素呢?取決於,手冊上這麼寫的:
Angular中所有的元素引用都會被jQuery或者jqLite包裝;他們永遠不是純DOM引用
所以Angular如果沒有檢測到jQuery,那麼就會使用jqLite元素,hide()方法值能用於jQuery元素,所以說這個示例程式碼只能當檢測到jQuery時才可以使用。如果你(不小心)修改了AngularJS和jQuery的出現順序,這個程式碼就會失效!雖說沒事挪指令碼的順序的事情不經常發生,但是在我開始模組化程式碼的時候確實給我造成了困擾。尤其是當你開始使用模組載入器(比如 RequireJS), 我的解決辦法是在配置裡顯示的宣告Angular確實依賴jQuery
另外一種方法就是你不要通過Angular元素的包裝來呼叫jQuery特定的方法,而是使用$(element).hide(4000)來表明自己的意圖。這樣依賴,即使修改了script載入順序也沒事。
壓縮
特別需要注意的是Angular應用壓縮問題。否則錯誤資訊比如 ‘Unknown provider:aProvider <- a’ 會讓你摸不到頭腦。跟其他很多東西一樣,這個錯誤在官方文件裡也是無從查起的。簡而言之,Angular依賴引數名來進行依賴注入。壓縮器壓根意識不到這個這跟Angular裡普通的引數名有啥不同,儘可能的把指令碼變短是他們職責。咋辦?用“友好壓縮法”來進行方法注入。看這裡:
1 2 3 |
module.service('myservice', function($http, $q) { // This breaks when minified }); |
to this:
1 2 3 |
module.service('myservice', [ '$http', '$q', function($http, $q) { // Using the array syntax to declare dependencies works with minification<b>!</b> }]); |
這個陣列語法很好的解決了這個問題。我的建議是從現在開始照這個方法寫,如果你決定壓縮JavaScript,這個方法可以讓你少走很多彎路。好像是一個automatic rewriter機制,我也不太清楚這裡面是怎麼工作的。
最終一點建議:如果你想用陣列語法複寫你的functions,在所有Angular依賴注入的地方應用之。包括directives,還有directive裡的controllers。別忘了逗號(經驗之談)
1 2 3 4 5 6 7 8 9 10 11 |
// the directive itself needs array injection syntax: module.directive('directive-with-controller', ['myservice', function(myservice) { return { controller: ['$timeout', function($timeout) { // but this controller needs array injection syntax, too! }], link : function(scope, element, attrs, ctrl) { } } }]); |
注意:link function不需要陣列語法,因為他並沒有真正的注入。這是被Angular直接呼叫的函式。Directive級別的依賴注入在link function裡也是使用的。
Directive永遠不會‘完成’
在directive中,一個令人掉頭髮的事就是directive已經‘完成’但你永遠不會知道。當把jQuery外掛整合到directive裡時,這個通知尤為重要。假設你想用ng-repeat把動態資料以jQuery datatable的形式顯示出來。當所有的資料在頁面中載入完成後,你只需要呼叫$(‘.mytable).dataTable()就可以了。 但是,臣妾做不到啊!
為啥呢?Angular的資料繫結是通過持續的digest迴圈實現的。基於此,Angular框架里根本沒有一個時間是‘休息’的。 一個解決方法就是將jQuery dataTable的呼叫放在當前digest迴圈外,用timeout方法就可以做到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
angular.module('table',[]).directive('mytable', ['$timeout', function($timeout) { return { restrict : 'E', template: '<table class="mytable">' + '<thead><tr><th>counting</th></tr></thead>' + '<tr ng-repeat="data in datas"><td></td></tr>' + '</table>', link : function(scope, element, attrs, ctrl) { scope.datas = ["one", "two", "three"] // Doesn't work, shows an empty table: // $('.mytable', element).dataTable() // But this does: $timeout(function() { $('.mytable', element).dataTable(); }, 0) } } }]); |
(例項程式碼 this plunkr )
在我們的程式碼裡甚至遇到過需要雙重巢狀$timeout。還有更瘋狂的就是新增<script>tag 到模板中,這個指令碼里回撥Angular的scope.$apply()方法。我只想說,這很不完美。基於Angular的實現機理,這很難改變。
儘管說了這麼多,Angular仍然是我最愛客戶端JS框架。你用Angular的時候遇到過其他的坑嗎?你用什麼方法解決這些問題的呢?請留言!