第一點區別是,ng-if
在後面表達式爲 true 的時候才建立這個 dom 節點,ng-show
是初始時就建立了,用 display:block
和 display:none
來控制顯示和不顯示。javascript
第二點區別是,ng-if
會(隱式地)產生新做用域,ng-switch
、 ng-include
等會動態建立一塊界面的也是如此。css
這樣會致使,在 ng-if
中用基本變量綁定 ng-model
,並在外層 div 中把此 model 綁定給另外一個顯示區域,內層改變時,外層不會同步改變,由於此時已是兩個變量了。html
<p>{{name}}</p> <div ng-if="true"> <input type="text" ng-model="name"> </div>
ng-show
不存在此問題,由於它不自帶一級做用域。前端
避免這類問題出現的辦法是,始終將頁面中的元素綁定到對象的屬性(data.x)而不是直接綁定到基本變量(x)上。java
會提示 Duplicates in a repeater are not allowed.
加 track by $index
可解決。固然,也能夠 trace by 任何一個普通的值,只要能惟一性標識數組中的每一項便可(創建 dom 和數據之間的關聯)。git
不止是 ng-click 中的表達式,只要是在頁面中,都不能直接調用原生的 JS 方法,由於這些並不存在於與頁面對應的 Controller 的 $scope 中。程序員
舉個栗子:github
<p>{{parseInt(55.66)}}<p>
會發現,什麼也沒有顯示。ajax
但若是在 $scope 中添加了這個函數:json
$scope.parseInt = function(x){ return parseInt(x); }
這樣天然是沒什麼問題了。
對於這種需求,使用一個 filter 或許是不錯的選擇:
<p>{{13.14 | parseIntFilter}}</p> app.filter('parseIntFilter', function(){ return function(item){ return parseInt(item); } })
{{now | 'yyyy-MM-dd'}}
這種表達式裏面,豎線和後面的參數經過什麼方式能夠自定義?filter,格式化數據,接收一個輸入,按某規則處理,返回處理結果。
ng 內置的 filter 有九種:
date(日期)
currency(貨幣)
limitTo(限制數組或字符串長度)
orderBy(排序)
lowercase(小寫)
uppercase(大寫)
number(格式化數字,加上千位分隔符,並接收參數限定小數點位數)
filter(處理一個數組,過濾出含有某個子串的元素)
json(格式化 json 對象)
filter 有兩種使用方法,一種是直接在頁面裏:
<p>{{now | date : 'yyyy-MM-dd'}}</p>
另外一種是在 js 裏面用:
// $filter('過濾器名稱')(須要過濾的對象, 參數1, 參數2,...) $filter('date')(now, 'yyyy-MM-dd hh:mm:ss');
// 形式 app.filter('過濾器名稱',function(){ return function(須要過濾的對象,過濾器參數1,過濾器參數2,...){ //...作一些事情 return 處理後的對象; } }); // 栗子 app.filter('timesFilter', function(){ return function(item, times){ var result = ''; for(var i = 0; i < times; i++){ result += item; } return result; } })
把 service 的方法和數據放在一個對象裏,並返回這個對象
app.factory('FooService', function(){ return { target: 'factory', sayHello: function(){ return 'hello ' + this.target; } } });
經過構造函數方式建立 service,返回一個實例化對象
app.service('FooService', function(){ var self = this; this.target = 'service'; this.sayHello = function(){ return 'hello ' + self.target; } });
建立一個可經過 config 配置的 service,$get 中返回的,就是用 factory 建立 service 的內容
app.provider('FooService', function(){ this.configData = 'init data'; this.setConfigData = function(data){ if(data){ this.configData = data; } } this.$get = function(){ var self = this; return { target: 'provider', sayHello: function(){ return self.configData + ' hello ' + this.target; } } } }); // 此處注入的是 FooService 的 provider app.config(function(FooServiceProvider){ FooServiceProvider.setConfigData('config data'); });
從底層實現上來看,service 調用了 factory,返回其實例;factory 調用了 provider,返回其 $get
中定義的內容。factory 和 service 功能相似,只不過 factory 是普通 function,能夠返回任何東西(return 的均可以被訪問,因此那些私有變量怎麼寫,你懂的);service 是構造器,能夠不返回(綁定到 this 的均可以被訪問);provider 是增強版 factory,返回一個可配置的 factory。
髒檢查機制。
雙向數據綁定是 AngularJS 的核心機制之一。當 view 中有任何數據變化時,會更新到 model ,當 model 中數據有變化時,view 也會同步更新,顯然,這須要一個監控。
原理就是,Angular 在 scope 模型上設置了一個 監聽隊列,用來監聽數據變化並更新 view 。每次綁定一個東西到 view 上時 AngularJS 就會往 $watch
隊列裏插入一條 $watch
,用來檢測它監視的 model 裏是否有變化的東西。當瀏覽器接收到能夠被 angular context 處理的事件時,$digest
循環就會觸發,遍歷全部的 $watch
,最後更新 dom。
舉個栗子
<button ng-click="val=val+1">increase 1</button>
click 時會產生一次更新的操做(至少觸發兩次 $digest
循環)
按下按鈕
瀏覽器接收到一個事件,進入到 angular context
$digest
循環開始執行,查詢每一個 $watch
是否變化
因爲監視 $scope
.val 的 $watch
報告了變化,所以強制再執行一次 $digest
循環
新的 $digest
循環未檢測到變化
瀏覽器拿回控制器,更新 $scope
.val 新值對應的 dom
$digest
循環的上限是 10 次(超過 10次後拋出一個異常,防止無限循環)。
這個問題換一種說法就是,如何在平級界面模塊間進行通訊。有兩種方法,一種是共用服務,一種是基於事件。
在 Angular 中,經過 factory 能夠生成一個單例對象,在須要通訊的模塊 a 和 b 中注入這個對象便可。
這個又分兩種方式
第一種是藉助父 controller。在子 controller 中向父 controller 觸發($emit
)一個事件,而後在父 controller 中監聽($on
)事件,再廣播($broadcast
)給子 controller ,這樣經過事件攜帶的參數,實現了數據通過父 controller,在同級 controller 之間傳播。
第二種是藉助 $rootScope
。每一個 Angular 應用默認有一個根做用域 $rootScope
, 根做用域位於最頂層,從它往下掛着各級做用域。因此,若是子控制器直接使用 $rootScope
廣播和接收事件,那麼就可實現同級之間的通訊。
對於小型項目,能夠按照文件類型組織,好比:
css js controllers models services filters templates
可是對於規模較大的項目,最好按業務模塊劃分,好比:
css modules account controllers models services filters templates disk controllers models services filters templates
modules 下最好再有一個 common 目錄來存放公共的東西。
做爲一個 MVVM 框架,Angular 應用自己就應該按照 模型,視圖模型(控制器),視圖來劃分。
這裏邏輯代碼的拆分,主要是指儘可能讓 controller 這一層很薄。提取共用的邏輯到 service 中 (好比後臺數據的請求,數據的共享和緩存,基於事件的模塊間通訊等),提取共用的界面操做到 directive 中(好比將日期選擇、分頁等封裝成組件等),提取共用的格式化操做到 filter 中等等。
在複雜的應用中,也能夠爲實體創建對應的構造函數,好比硬盤(Disk)模塊,可能有列表、新建、詳情這樣幾個視圖,並分別對應的有 controller,那麼能夠建一個 Disk 構造函數,裏面完成數據的增刪改查和驗證操做,有跟 Disk 相關的 controller,就注入 Disk 構造器並生成一個實例,這個實例就具有了增刪改查和驗證方法。這樣既井井有條,又實現了複用(讓 controller 層更薄了)。
Angular1.x 中經常使用 ngRoute 和 ui.router,還有一種爲 Angular2 設計的 new router(面向組件)。後面那個沒在實際項目中用過,就不講了。
不管是 ngRoute 仍是 ui.router,做爲框架額外的附加功能,都必須以 模塊依賴 的形式被引入。
ngRoute 模塊是 Angular 自帶的路由模塊,而 ui.router 模塊是基於 ngRoute模塊開發的第三方模塊。
ui.router 是基於 state (狀態)的, ngRoute 是基於 url 的,ui.router模塊具備更強大的功能,主要體如今視圖的嵌套方面。
使用 ui.router 可以定義有明確父子關係的路由,並經過 ui-view 指令將子路由模版插入到父路由模板的 <div ui-view></div>
中去,從而實現視圖嵌套。而在 ngRoute 中不能這樣定義,若是同時在父子視圖中 使用了 <div ng-view></div>
會陷入死循環。
ngRoute
var app = angular.module('ngRouteApp', ['ngRoute']); app.config(function($routeProvider){ $routeProvider .when('/main', { templateUrl: "main.html", controller: 'MainCtrl' }) .otherwise({ redirectTo: '/tabs' });
ui.router
var app = angular.module("uiRouteApp", ["ui.router"]); app.config(function($urlRouterProvider, $stateProvider){ $urlRouterProvider.otherwise("/index"); $stateProvider .state("Main", { url: "/main", templateUrl: "main.html", controller: 'MainCtrl' })
沒有本身用 directive 作過一全套組件,講不出。
能想到的一點是,組件如何與外界進行數據的交互,以及如何經過簡單的配置就能使用吧。
可能會遇到不一樣模塊之間的衝突。
好比一個團隊全部的開發在 moduleA 下進行,另外一團隊開發的代碼在 moduleB 下
angular.module('myApp.moduleA', []) .factory('serviceA', function(){ ... }) angular.module('myApp.moduleB', []) .factory('serviceA', function(){ ... }) angular.module('myApp', ['myApp.moduleA', 'myApp.moduleB'])
會致使兩個 module 下面的 serviceA 發生了覆蓋。
貌似在 Angular1.x 中並無很好的解決辦法,因此最好在前期進行統一規劃,作好約定,嚴格按照約定開發,每一個開發人員只寫特定區塊代碼。
致使學習成本較高,對前端不友好。
但遵照 AngularJS 的約定時,生產力會很高,對 Java 程序員友好。
由於全部內容都是動態獲取並渲染生成的,搜索引擎無法爬取。
一種解決辦法是,對於正經常使用戶的訪問,服務器響應 AngularJS 應用的內容;對於搜索引擎的訪問,則響應專門針對 SEO 的HTML頁面。
做爲 MVVM 框架,由於實現了數據的雙向綁定,對於大數組、複雜對象會存在性能問題。
能夠用來 優化 Angular 應用的性能 的辦法:
減小監控項(好比對不會變化的數據採用單向綁定)
主動設置索引(指定 track by
,簡單類型默認用自身當索引,對象默認使用 $$hashKey
,好比改成 track by item.id
)
下降渲染數據量(好比分頁,或者每次取一小部分數據,根據須要再取)
數據扁平化(好比對於樹狀結構,使用扁平化結構,構建一個 map 和樹狀數據,對樹操做時,因爲跟扁平數據同一引用,樹狀數據變動會同步到原始的扁平數據)
另外,對於Angular1.x ,存在 髒檢查 和 模塊機制 的問題。
可嘗試 Ionic,但並不完善。
在 angular 1.2 之前,在 view 上的任何綁定都是直接綁定在 $scope
上的
function myCtrl($scope){ $scope.a = 'aaa'; $scope.foo = function(){ ... } }
使用 controllerAs,不須要再注入 $scope
,controller 變成了一個很簡單的 javascript 對象(POJO),一個更純粹的 ViewModel。
function myCtrl(){ // 使用 vm 捕獲 this 可避免內部的函數在使用 this 時致使上下文改變 var vm = this; vm.a = 'aaa'; }
從源碼實現上來看,controllerAs 語法只是把 controller 這個對象的實例用 as 別名在 $scope 上建立了一個屬性。
if (directive.controllerAs) { locals.$scope[directive.controllerAs] = controllerInstance; }
可是這樣作,除了上面提到的使 controller 更加 POJO 外,還能夠避免遇到 AngularJS 做用域相關的一個坑(就是上文中 ng-if 產生一級做用域的坑,其實也是 javascript 原型鏈繼承中值類型繼承的坑。由於使用 controllerAs 的話 view 上全部字段都綁定在一個引用的屬性上,好比 vm.xx,因此坑再也不存在)。
<div ng-controller="TestCtrl as vm"> <p>{{name}}</p> <div ng-if="vm.name"> <input type="text" ng-model="vm.name"> </div> </div>
使用 controllerAs 會遇到的一個問題是,由於沒有注入 $scope
,致使 $emit
、 $broadcast
、 $on
、 $watch
等 $scope
下的方法沒法使用。這些跟事件相關的操做能夠封裝起來統一處理,或者在單個 controller 中引入 $scope
,特殊對待。
依賴注入是一種軟件設計模式,目的是處理代碼之間的依賴關係,減小組件間的耦合。
舉個栗子,若是沒有使用 AngularJS,想從後臺查詢數據並在前端顯示,可能須要這樣作:
var animalBox = document.querySelector('.animal-box'); var httpRequest = { get: function(url, callback){ console.log(url + ' requested'); var animals = ['cat', 'dog', 'rabbit']; callback(animals); } } var render = function(el, http){ http.get('/api/animals', function(animals){ el.innerHTML = animals; }) } render(httpRequest, animalBox);
可是,若是在調用 render 的時候不傳參數,像下面這樣,會報錯,由於找不到 el 和 http(定義的時候依賴了,運行的時候不會自動查找依賴項)
render();
// TypeError: Cannot read property 'get' of undefined
而使用 AngularJS,能夠直接這樣
function myCtrl = ($scope, $http){ $http.get('/api/animals').success(function(data){ $scope.animals = data; }) }
也就是說,在 Angular App 運行的時候,調用 myCtrl,自動作了 $scope
和 $http
兩個依賴性的注入。
AngularJS 是經過構造函數的參數名字來推斷依賴服務名稱的,經過 toString()
來找到這個定義的 function 對應的字符串,而後用正則解析出其中的參數(依賴項),再去依賴映射中取到對應的依賴,實例化以後傳入。
簡化一下,大概是這樣:
var inject = { // 存儲依賴映射關係 storage: {}, // 註冊依賴 register: function(name, resource){ this.storage[name] = resource; }, // 解析出依賴並調用 resolve: function(target){ var self = this; var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; fnText = target.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS)[1].split(/, ?/g); var args = []; argDecl.forEach(function(arg){ if(self.storage[arg]){ args.push(self.storage[arg]); } }) return function(){ target.apply({}, args); } } }
使用這個 injector,前面那個不用 AngularJS 的栗子這樣改造一下就能夠調用了
inject.register('el', animalBox); inject.register('ajax', httpRequest); render = inject.resolve(render); render();
由於 AngularJS 的 injector 是假設函數的參數名就是依賴的名字,而後去查找依賴項,那若是按前面栗子中那樣注入依賴,代碼壓縮後(參數被重命名了),就沒法查找到依賴項了。
// 壓縮前 function myCtrl = ($scope, $http){ ... } // 壓縮後 function myCtrl = (a, b){ ... }
因此,一般會使用下面兩種方式注入依賴(對依賴添加的順序有要求)。
數組註釋法
myApp.controller('myCtrl', ['$scope', '$http', function($scope, $http){ ... }])
顯式 $inject
myApp.controller('myCtrl', myCtrl); function myCtrl = ($scope, $http){ ... } myCtrl.$inject = ['$scope', '$http'];
對於一個 DI 容器,必須具有三個要素:依賴項的註冊,依賴關係的聲明和對象的獲取。
在 AngularJS 中,module 和 $provide 均可以提供依賴項的註冊;內置的 injector 能夠獲取對象(自動完成依賴注入);依賴關係的聲明,就是前面問題中提到的那樣。
下面是個栗子
// 對於 module,傳遞參數不止一個,表明新建模塊,空數組表明不依賴其餘模塊 // 只有一個參數(模塊名),表明獲取模塊 // 定義 myApp,添加 myApp.services 爲其依賴項 angular.module('myApp', ['myApp.services']); // 定義一個 services module,將 services 都註冊在這個 module 下面 angular.module('myApp.services', []) // $provider 有 factory, service, provider, value, constant // 定義一個 HttpService angular.module('myApp.services').service('HttpService', ['$http', function($http){ ... }])
相比 Angular1.x,Angular2的改動很大,幾乎算是一個全新的框架。
基於 TypeScript(可使用 TypeScript 進行開發),在大型項目團隊協做時,強語言類型更有利。
組件化,提高開發和維護的效率。
還有 module 支持動態加載,new router,promise的原生支持等等。
迎合將來標準,吸納其餘框架的優勢,值得期待,不過同時要學習的東西也更多了(ES next、TS、Rx等)。