孵了八個月,AngularJS 1.3版終於在前幾天破殼而出~
一直很期待的ngModelOptions updateOn功能隨著1.3版問市,未來繫結到<input>可指定移開焦點才觸發,比每敲一個字母重算一次有效率,另外也可選擇debouncing累積多次變化只重算一次(相當於KO的throttle擴充方法),這些都是之前在寫KO MVVM慣用的做法,1.3起正式支援。
另外還看到一些亮點:
- ngMessage
類似我鍾愛的內嵌式欄位檢核訊息樣式。demo:https://yearofmoo.github.io/ngMessages/ - One-time binding
只繫結一次,後續不追蹤變化。遇有大量來自ViewModel但不變動的內容,改用一次性繫結將能大幅改善效能。
有這些新武器,Angular就更好用了。
迫不及待地把進行中專案的Angular從1.2.25升到1.3。薑!薑!薑!薑~ 換裝新引擎的網站… 整個爛掉了! 無法建立Controller,大小繫結隨之全軍覆沒… orz
才一升級就被迫觀摩新版Source Code,莫非是程式魔人的宿命。使用消去法逐一拔掉專案模組縮小範圍,最後鎖定一段參考stackoverflow討論加入的$controller Decorator邏輯,如下所示:Demo
<!DOCTYPEhtml>
<htmlng-app="app">
<head>
<metacharset="utf-8">
<title>Controller Decorator</title>
</head>
<bodyng-controller="myCtrl">
<div>{{test}}</div>
<scriptsrc="//code.angularjs.org/1.3.0/angular.js"></script>
<script>
//http://stackoverflow.com/questions/23382734/angularjs-get-controller-name-from-scope
angular.module("app",[])
.config(["$provide", function($provider) {
$provider.decorator("$controller", [
"$delegate", function($delegate) {
returnfunction(constructor, locals) {
//do nothing, just to test decorator
return $delegate(constructor, locals);
}
}
])
}])
.controller("myCtrl", function ($scope) {
$scope.test = "TEST";
});
</script>
</body>
</html>
程式執行會出現以下錯誤,Controller建立失敗,後面全都不用玩:
"TypeError: object is not a function
at https://code.angularjs.org/1.3.0/angular.js:7594:13
at forEach (https://code.angularjs.org/1.3.0/angular.js:343:20)
at nodeLinkFn (https://code.angularjs.org/1.3.0/angular.js:7593:11)
at compositeLinkFn (https://code.angularjs.org/1.3.0/angular.js:6991:13)
at compositeLinkFn (https://code.angularjs.org/1.3.0/angular.js:6994:13)
at publicLinkFn (https://code.angularjs.org/1.3.0/angular.js:6870:30)
at https://code.angularjs.org/1.3.0/angular.js:1489:27
at Scope.$eval (https://code.angularjs.org/1.3.0/angular.js:14123:28)
at Scope.$apply (https://code.angularjs.org/1.3.0/angular.js:14221:23)
at bootstrapApply (https://code.angularjs.org/1.3.0/angular.js:1487:15)"
將angular換成1.2.25或將$provider.decorator()片段,問題就會消失。迫不得已,開始Line By Line Debug追蹤,比對加上及移除$provider.decorator("$controller", …)的行為差異,最後找到一處關鍵:
加入decorator時,later = false;未加時,later = true。再往源頭找,真相大白:
angular.js 1.2.25版
/**
* @ngdoc service
* @name $controller
* @requires $injector
*
* @param {Function|string} constructor If called with a function then it's considered to be the
* controller constructor function. Otherwise it's considered to be a string which is used
* to retrieve the controller constructor using the following steps:
*
* * check if a controller with given name is registered via `$controllerProvider`
* * check if evaluating the string on the current scope returns a constructor
* * check `window[constructor]` on the global `window` object
*
* @param {Object} locals Injection locals for Controller.
* @return {Object} Instance of given controller.
*
* @description
* `$controller` service is responsible for instantiating controllers.
*
* It's just a simple call to {@link auto.$injector $injector}, but extracted into
* a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
*/
returnfunction(expression, locals) {
var instance, match, constructor, identifier;
angular.js 1.3.0版
/**
* @ngdoc service
* @name $controller
* @requires $injector
*
* @param {Function|string} constructor If called with a function then it's considered to be the
* controller constructor function. Otherwise it's considered to be a string which is used
* to retrieve the controller constructor using the following steps:
*
* * check if a controller with given name is registered via `$controllerProvider`
* * check if evaluating the string on the current scope returns a constructor
* * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
* `window` object (not recommended)
*
* @param {Object} locals Injection locals for Controller.
* @return {Object} Instance of given controller.
*
* @description
* `$controller` service is responsible for instantiating controllers.
*
* It's just a simple call to {@link auto.$injector $injector}, but extracted into
* a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
*/
returnfunction(expression, locals, later, ident) {
// PRIVATE API:
// param `later` --- indicates that the controller's constructor is invoked at a later time.
// If true, $controller will allocate the object with the correct
// prototype chain, but will not invoke the controller until a returned
// callback is invoked.
// param `ident` --- An optional label which overrides the label parsed from the controller
// expression, if any.
var instance, match, constructor, identifier;
1.3.0版的$controller()增加兩個新參數later及ident,而先前decorator()程式針對1.2.X撰寫,只有expression與locals,傳遞到$delegate時漏了later及ident,導致跑錯邏輯。$controller被定義成Private API,故Angluar自由調整無可厚非,但decorator()無可避免與其產生相依,因而被波及。
知道原因,稍加修改補上later及ident,裝妥Angular 1.3新引擎的專案就順利起飛囉~
- 心得1:追蹤及修改3rd Party Source Code好像已經變成前端攻城獅的必要技能
- 心得2:某些追求程式省時省工便於修改擴充的巧妙解法,在Library更動時挺易碎裂,不如Copy & Paste之類愚公移山法強韌。但我仍傾向前者,只要遇到問題能修正,對照平時省下的工程及對架構的簡化,仍然很值得。