在Angular群里回答新手問題一段時間了,有一些Angular方面的坑留在這里備查,希望能對各位有所幫助。這個文章將來會隨時更新,不會單獨開新章,歡迎各位訂閱。
Q1.<div ng-include="views/user/show.html"></div> 錯在哪里?
如果你這么寫過,會發現這個位置啥也沒有加載出來,那么,錯在哪里呢?錯在ng-include需要的是一個變量,如果你在$scope中有這樣一個變量 $scope.userShowTemplateUrl = "views/users/show.html",並且把上面這句變為<div ng-include="userShowTemplateUrl"></div>就能正常工作了。或者這樣寫也行:<div ng-include=" 'views/user/show.html' "></div>
原因何在?
因為在ng-include中,是把它的參數當做變量來解釋的,它會通過$eval對傳入的值進行計算,然后作為模板地址去加載。
不過,更好的方法是把這些界面片段(partical)寫成指令,那樣你就不用在多處重復寫路徑了,更重要的是,將來你可以直接在這里擴展它的交互邏輯,從界面原型平滑的演化到線上系統。
Q2. 我的指令怎么無效?
A2. 如果你排除了代碼錯誤等問題,那么最可能的原因是restrict。restrict參數是用來規定你可以通過哪種方式來使用指令,而這個問題之所以容易 成為坑,是因為restrict的默認值是A,也就是說,默認情況下,指令只能通過屬性的形式使用,比如我寫了一個指令叫做appHeader,那么默認 情況下我只能用這樣的形式使用它:<div app-header></div>,而<app-header></app-header>的形式則是無效的。
所以,如果你用返回函數的形式使用指令,那么你就只能使用屬性的方式調用它,比如:
yourModule.directive('appHeader', function() { return function(scope, element, attrs) { element.text('hello'); } });
如果要使用元素的方式使用指令,那么你就要這樣寫:
yourModule.directive('appHeader', function() { return { restrict: 'E', // 或'EA'等都可以,幾種形式可以任意組合 link: function(scope, element, attrs) { element.text('hello'); } } });
Q3. 修改了變量怎么界面沒反應?
A3. 首先你當然要檢查有沒有錯誤以及是否確實是scope變量,如果這些都沒問題,那么多半兒是$apply導致的。對於大多數操作,$apply都會自動執 行,所以你不用擔心,但是如果你使用了angular之外的功能,比如直接調用了setTimeout函數、掛接了jquery的事件、使用了 jquery的ajax操作等等,那么系統就沒有機會幫你調用$apply,界面也就沒有機會刷新了,但是你如果之后又做了其他會導致$apply的操 作,你會發現以前“欠下”的那次界面刷新被正常執行了了 …… 遲到的刷新仍然是bug。
典型代碼如下:
setTimeout(function() { $scope.time = new Date() }, 1000);
這種情況下你在頁面中綁定的time變量將不會被自動刷新,無論是通過{{}}表達式,還是通過ng-*屬性或者其他任何形式。怎么改呢?這樣:
setTimeout(function() { $scope.$apply(function() { $scope.time = new Date(); }); }, 1000);
不過,這不是最好的形式,最好的形式是什么呢?當然是使用angular內置的$timeout服務,它就是干這個的:
$timeout(function() { $scope.time = new Date(); });
沒有$apply,卻正常工作,沒bug,而且漂亮多了吧?不過這里別忘了你得把$timeout服務進行依賴注入,不然它是undefined。
Q4. ng-click 寫成 ng-class 導致的界面停止響應
A4. 這是我自己犯過的一個低級錯誤,屬於深度依賴ide導致的問題。ide的自動代碼提示功能,ng-cl的第一個候選項是ng-class,如果偷懶少打了 一個字,那么本來想寫ng-click的代碼就會寫成ng-class,結果就是,無休止的重新計算ng-class中的表達式,其中的原因還沒來得及看 源碼研究。
如果遇到界面停止響應的問題,而且你也同樣深度依賴ide,那么,從這個角度查查看吧。
Q5. 我知道你不拜金,但別忘了$
A5. 在angular中有一個通用的約定:angular的內部服務、方法、屬性通常都會以$開頭,而相應的,它也要求你自己的命名不要用$開頭。比較容易忘 記用$開頭的主要是一些方法,特別是$apply, $watch, $on, $broardcast, $emit這些,而這些如果你寫錯了,在chrome中你將得到一個莫名其妙的提示 TypeError: undefined is not a function! 可惡的是,連函數名字都沒有!所以,雖然我知道你不拜金,但是千萬不要忘了寫$!
Q6. 注意作用域的原型繼承問題!
A6. 在Angular中,作用域是通過原型鏈進行繼承的。而這種繼承有一個問題,那就是在子類中對變量進行賦值時,不會去修改父級的。
假設scopeA繼承自scopeB,而在scopeB中定義了一個變量value: 1,這時候,讀取scopeA.value可以正確取到值,但是如果賦值,就有問題了 scopeA.value = 2,這時候,scopeB.value的值是多少呢?你可能以為是2,但它是1!原因就在於原型繼承時對變量的賦值不會修改原型中的值,而是直接在當前scope中創建一個同名的屬性。
這個現象導致了一個容易讓新手困惑的問題:
下面的代碼工作正常:
<label><input type="radio" ng-model="color" value="red"> Red </label><br/> <label><input type="radio" ng-model="color" value="green"> Green</label><br/> <label><input type="radio" ng-model="color" value="blue"> Blue </label><br/> {{color}}
而下面的代碼工作不正常,color值將不會隨着選擇而變化:
<div ng-repeat="value in ['red', 'green', 'blue']"> <label><input type="radio" ng-model="color" ng-value="value"> {{value}} </label> </div> {{color}}
它的原因就在於color值定義在當前scope中,而ng-repeat創建了一個子scope,它使用原型繼承的方式從父級繼承下來。對color值的修改,會去修改子級的變量,而父級的同名變量不會被修改。
要想讓它正常工作,就改成這樣:
<div ng-repeat="value in ['red', 'green', 'blue']"> <label><input type="radio" ng-model="vm.color" ng-value="value"> {{value}} </label> </div> {{vm.color}}
這里,把color定義成了一個vm對象的屬性,這時候,因為只需要對vm的成員進行賦值,而不存在對vm進行賦值的情 況,所以賦值會正確的作用於父級scope上。這里的vm只是$scope上的一個對象,叫別的名字也可以,只是因為它的實際作用是ViewModel, 所以我習慣於把它命名為vm。
在Angular 1.2以后的版本中引入了controllerAs語法,可以一勞永逸的解決這個問題。具體的用法請參見 http://www.cnblogs.com/whitewolf/p/3493362.html
如果使用1.2以下的版本,可以在controller的第一句加上這樣的語法來模擬:
var vm = $scope.vm = {};
所有的$scope變量都改為賦值給vm變量就可以了,view中也要相應的引用vm變量。
Q7. angular.module的兩種寫法:含義大不同
angular.module('name', [])和angular.module('name') 雖然看起來很相似,但是!它們的含義卻是截然不同的!
angular.module('name', [])是創建一個新的module,[]表示它沒有依賴任何其他模塊,如果已經有了一個同名模塊,則會覆蓋現有的。
而angular.module('name')是查找一個現有module,如果這個module不存在,則返回空值。
如果把帶方括號的形式(創建)誤用為不帶方括號的形式(引用),那么在它的返回值上調用controller等函數會出現空指針錯誤。
而如果把引用形式誤用為創建形式,則會導致難以理解的“對象不存在”錯誤,但是你卻明明定義過那個service或者controller等對象!這種問題就是因為后面的模塊定義覆蓋了以前的模塊定義,你定義過的那些對象都被隨着以前的module而丟掉了!
Q8. 第三方指令不起作用
最可能的原因是你沒有加入模塊依賴。第三方指令通常會定義在自己的模塊中,所以這個模塊必須被你的app模塊所依賴,其中包含的指令才能在view中使用。比如你要使用ui-select2指令,就必須在自己的模塊定義中加入這個依賴:
angular.module('app', [ 'ngCookies', 'ngResource', 'ngSanitize', 'ui.select2' ]).....
如果仍然沒有解決問題,請看看控制台中有沒有錯誤信息,以及是否存在Q2所提的情況。
