【轉】Angular學習總結--很詳細的教程

*這篇文章是轉來的,作了本身的一點修改,排版。原始出處不明,如涉及原博主版權問題,請及時告知,我將會當即刪除*。javascript

 

1 前言

  前端技術的發展是如此之快,各類優秀技術、優秀框架的出現簡直讓人應接不暇,緊跟時代潮流,學習掌握新知識天然是不敢怠慢。css

  AngularJS是google在維護,其在國外已經十分火熱,但是國內的使用狀況卻有不小的差距,參考文獻/網絡文章也很匱乏。這裏便將我學習AngularJS寫成文檔,一方面做爲本身學習路程上的記錄,另外一方面也給有興趣的同窗一些參考。html

  首先我本身也是一名學習者,會以學習者的角度來整理個人行文思路,這裏可能只是些探索,有理解或是技術上的錯誤還請你們指出;其次我特別喜歡編寫小例子來把一件事情說明白,故在文中會盡量多的用示例加代碼講解,我相信這會是一種比較好的方式;最後,我深知AngularJS的使用方式跟jquery的使用方式有很大不一樣,在你們都有jquery、ext經驗的條件下對於angular的學習會困難重重,不過我更相信在你們的堅持下,可以快速的學好AngularJS,至少咱也能深刻了解到AngularJS的基本思想,對我們之後本身的插件開發、項目開發都會有很大的啓示。前端

 

2 AngularJS概述

2.1 AngularJS是什麼?

  AngularJs(後面就簡稱ng了)是一個用於設計動態web應用的結構框架。首先,它是一個框架,不是類庫,是像EXT同樣提供一整套方案用於設計web應用。它不只僅是一個javascript框架,由於它的核心實際上是對HTML標籤的加強。java

  何爲HTML標籤加強?其實就是使你可以用標籤完成一部分頁面邏輯,具體方式就是經過自定義標籤、自定義屬性等,這些HTML原生沒有的標籤/屬性在ng中有一個名字:指令(directive)。後面會詳細介紹。那麼,什麼又是動態web應用呢?與傳統web系統相區別,web應用能爲用戶提供豐富的操做,可以隨用戶操做不斷更新視圖而不進行url跳轉。ng官方也聲明它更適用於開發CRUD應用,即數據操做比較多的應用,而非是遊戲或圖像處理類應用。jquery

  爲了實現這些,ng引入了一些很是棒的特性,包括模板機制、數據綁定、模塊、指令、依賴注入、路由。經過數據與模板的綁定,可以讓咱們擺脫繁瑣的DOM操做,而將注意力集中在業務邏輯上。git

  另一個疑問,ng是MVC框架嗎?仍是MVVM框架?官網有提到ng的設計採用了MVC的基本思想,而又不徹底是MVC,由於在書寫代碼時咱們確實是在用ng-controller這個指令(起碼從名字上看,是MVC吧),但這個controller處理的業務基本上都是與view進行交互,這麼看來又很接近MVVM。讓咱們把目光移到官網那個非醒目的title上:「AngularJS — Superheroic JavaScript MVW Framework」。angularjs

 

2.2 AngularJS簡單介紹

  AngularJS 從新定義了前端應用的開發方式。面對HTML和JavaScript之間的界線,它非但不畏縮不前,反而正面出擊,提出了有效的解決方案。github

  不少前端應用的開發框架,好比Backbone、EmberJS等,都要求開發者繼承此框架特有的一些JavaScript對象。這種方式有其長處,但它沒必要要地污染了開發者本身代碼的對象空間,還要求開發者去了解內存裏那些抽象對象。儘管如此咱們仍是接受了這種方式,由於網絡最初的設計沒法提供咱們今天所需的交互性,因而咱們須要框架,來幫咱們填補JavaScript和HTML之間的鴻溝。並且有了它,你不用再「直接」操控DOM,只要給你的DOM註上metadata(即AngularJS裏的directive們),而後讓AngularJS來幫你操縱DOM。同時,AngularJS不依賴(也不妨礙)任何其餘的框架。你甚至能夠基於其它的框架來開發AngularJS應用。web

  API地址:http://docs.angularjs.org/api/

  AngularJS入門教程:https://github.com/zensh/AngularjsTutorial_cn

  AngularJS開發指南:https://github.com/MaxSherry/Angularjs-Developer-Guide

 

2.3 何時該用AngularJS

  AngularJS是一個 MV* 框架,最適於開發客戶端的單頁面應用。它不是個功能庫,而是用來開發動態網頁的框架。它專一於擴展HTML的功能,提供動態數據綁定(data binding),並且它能跟其它框架(如jQuery)合做融洽。

  若是你要開發的是單頁應用,AngularJS就是你的上上之選。Gmail、Google Docs、Twitter和Facebook這樣的應用,都很能發揮AngularJS的長處。可是像遊戲開發之類對DOM進行大量操縱、又或者單純須要極高運行速度的應用,就不是AngularJS的用武之地了

 

3 AugularJS特性

  AngularJS是一個新出現的強大客戶端技術,提供給你們的一種開發強大應用的方式。這種方式利用而且擴展HTML,CSS和javascript,而且彌補了它們的一些很是明顯的不足。本應該使用HTML來實現而如今由它開發的動態一些內容。

  AngularJS有五個最重要的功能和特性:

 

3.1 特性一:雙向的數據綁定

  數據綁定多是AngularJS最酷最實用的特性。它可以幫助你避免書寫大量的初始代碼從而節約開發時間。一個典型的web應用可能包含了80%的代碼用來處理,查詢和監聽DOM。數據綁定是的代碼更少,你能夠專一於你的應用。

  咱們想象一下Model是你的應用中的簡單數據。你的Model是你用來讀取或者更新的部分。數據綁定指令提供了你的Model投射到view的方法。這些投射能夠無縫的,絕不影響的應用到web應用中。

  傳統來講,當Model變化了。 開發人員須要手動處理DOM元素而且將屬性反映到這些變化中。這個一個雙向的過程。一方面,model變化驅動了DOM中元素變化,另外一方面,DOM元素的變化也會影響到Model。這個在用戶互動中更加複雜,由於開發人員須要處理和解析這些互動,而後融合到一個model中,而且更新View。這是一個手動的複雜過程,當一個應用很是龐大的時候,將會是一件很是費勁的事情。

  這裏確定有更好的解決方案!那就是AngularJS的雙向數據綁定,可以同步DOM和Model等等。

  這裏有一個很是簡單的例子,用來演示一個input輸入框和<h1>元素的雙向綁定(例01):

<!-- 例01 -->
<!doctype html>
<html ng-app="demoApp">
    <head>
        <script src="./js/angular.min.js"></script>
    </head>

    <body>
        <div>
            <label>Name:</label>
            <input type="text" ng-model="user.name" placeholder="請輸入名字">
            <hr>
            <h1>Hello, {{user.name}}!</h1>
        </div>
    </body>
</html>

 

3.2 特性二:模板

  在AngularJS中,一個模板就是一個HTML文件。可是HTML的內容擴展了,包含了不少幫助你映射model到view的內容。

  HTML模板將會被瀏覽器解析到DOM中。DOM而後成爲AngularJS編譯器的輸入。AngularJS將會遍歷DOM模板來生成一些指導,即,directive(指令)。全部的指令都負責針對view來設置數據綁定。

  咱們要理解AuguarJS並不把模板當作String來操做。輸入AngularJS的是DOM而非string。數據綁定是DOM變化,不是字符串的鏈接或者innerHTML變化。使用DOM做爲輸入,而不是字符串,是AngularJS區別於其它的框架的最大緣由。使用DOM容許你擴展指令詞彙而且能夠建立你本身的指令,甚至開發可重用的組件。

  最大的好處是爲設計師和開發者建立了一個緊密的工做流。設計師能夠像往常同樣開發標籤,而後開發者拿過來添加上功能,經過數據綁定將會使得這個過程很是簡單。

  這裏有一個例子,咱們使用ng-repeat指令來循環圖片數組而且加入img模板,以下:

function AlbumCtrl($scope) { scope.images = [ {"image":"img/image_01.png", "description":"Image 01 description"}, {"image":"img/image_02.png", "description":"Image 02 description"}, {"image":"img/image_03.png", "description":"Image 03 description"}, {"image":"img/image_04.png", "description":"Image 04 description"}, {"image":"img/image_05.png", "description":"Image 05 description"} ]; } <div ng-controller="AlbumCtrl">
  <ul>
    <li ng-repeat="image in images">
      <img ng-src="{{image.image}}" alt="{{image.description}}">
    </li>
  </ul>
</div>

這裏還有一件事值得提一句,AngularJS並不強制你學習一個新的語法或者從你的應用中提出你的模板。

 

3.3 特性三:MVC

  針對客戶端應用開發AngularJS吸取了傳統的MVC基本原則。MVC或者Model-View-Controll設計模式針對不一樣的人可能意味不一樣的東西。AngularJS並不執行傳統意義上的MVC,更接近於MVVM(Moodel-View-ViewModel)。 

  Model

  model是應用中的簡單數據。通常是簡單的javascript對象。這裏沒有必要繼承框架的classes,使用proxy對象封裝或者使用特別的setter/getter方法來訪問。事實上咱們處理vanilla javascript的方法就是一個很是好的特性,這種方法使得咱們更少使用應用的原型。

  ViewModel

  viewmodel是一個用來提供特別數據和方法從而維護指定view的對象。viewmodel是$scope的對象,只存在於AnguarJS的應用中。$scope只是一個簡單的js對象,這個對象使用簡單的API來偵測和廣播狀態變化。

  Controller

  controller負責設置初始狀態和參數化$scope方法用以控制行爲。須要指出的controller並不保存狀態也不和遠程服務互動。

  View

  view是AngularJS解析後渲染和綁定後生成的HTML 。這個部分幫助你建立web應用的架構。$scope擁有一個針對數據的參考,controller定義行爲,view處理佈局和互動。

 

3.4 特性四:服務和依賴注入

  AngularJS服務其做用就是對外提供某個特定的功能

  AngularJS擁有內建的依賴注入(DI)子系統,能夠幫助開發人員更容易的開發,理解和測試應用。DI容許你請求你的依賴,而不是本身找尋它們。好比,咱們須要一個東西,DI負責找建立而且提供給咱們。

  爲了獲得核心的AngularJS服務,只須要添加一個簡單服務做爲參數,AngularJS會偵測而且提供給你:

function EditCtrl($scope, $location, $routeParams) { // Something clever here...
} //你也能夠定義本身的服務而且讓它們注入: //定義服務
angular.module('MyServiceModule', []). factory('notify', ['$window', function (win) { return function (msg) { win.alert(msg); }; }]); //使用
function myController(scope, notifyService) { scope.callNotify = function (msg) { notifyService(msg); }; } //注入
myController.$inject = ['$scope', 'notify']; 

 

3.5 特性五:指令(Directives)

  指令是我我的最喜歡的特性。你是否是也但願瀏覽器能夠作點兒有意思的事情?那麼AngularJS能夠作到。

  指令能夠用來建立自定義的標籤。它們能夠用來裝飾元素或者操做DOM屬性。能夠做爲標籤、屬性、註釋和類名使用。

  這裏是一個例子,它監聽一個事件而且針對的更新它的$scope ,以下:

myModule.directive('myComponent', function(mySharedService) { return { restrict: 'E',
controller:
function($scope, $attrs, mySharedService) { $scope.$on('handleBroadcast', function() { $scope.message = 'Directive: ' + mySharedService.message; }); }, replace: true, template: '<input>' }; }); //而後,你能夠使用這個自定義的directive來使用: <my-component ng-model="message"></my-component>

使用一系列的組件來建立你本身的應用將會讓你更方便的添加,刪除和更新功能。

 

4 功能介紹

4.1數據綁定

  AngularJS的雙向數據綁定,意味着你能夠在Mode(JS)中改變數據,而這些變更馬上就會自動出如今View上,反之亦然。即:一方面能夠作到model變化驅動了DOM中元素變化,另外一方面也能夠作到DOM元素的變化也會影響到Model。

  在咱們使用jQuery的時候,代碼中會大量充斥相似這樣的語句:var val = $(‘#id’).val(); $(‘#id’).html(str);等等,即頻繁的DOM操做(讀取和寫入),其實咱們的最終目的並非要操做DOM,而是要實現業務邏輯。ng的綁定將讓你擺脫DOM操做,只要模板與數據經過聲明進行了綁定,二者將隨時保持同步,最新的數據會實時顯示在頁面中,頁面中用戶修改的數據也會實時被記錄在數據模型中。

  從View到Controller再到View的數據交互(例01):

<html ng-app="demoApp">

……
  
<input type="text" ng-model="user.name" placeholder="請輸入名稱"/>   Hello, {{ user.name }}!
……

  關鍵: ng-app 、 ng-model 和 { {user.name } } 

  首先: <html>元素的ng-app屬性。標識這個DOM裏面的內容將啓用AngularJS應用。

  其次:告訴AngularJS,對頁面上的「user.name」 這個Model進行雙向數據綁定。

  第三:告訴AngularJS,在「{{ user.name}}」這個指令模版上顯示「user.name」這個Model的數據。

 

  從Server到Controller再到View的數據交互(例02):

<html ng-app="demoApp"> ……   <div ng-controller="demoController">
    <input type="text" ng-model="user.name" disabled="disabled"/>
    <a href="javascript:void(0);" ng-click="getAjaxUser()">AJAX獲取名字</a>
  </div>
……
demoApp.controller("demoController", function($http, $scope){   $scope. getAjaxUser = function(){     $http.get({url:"../xxx.action"})
      .success(function(data){
        $scope.user= data;       });     $scope.user = {"name":"從JOSN中獲取的名稱","age":22};   }; });

  改變$scope中的user,View也會自動更新。

 

4.2 scopes、module、controller 

4.2.1 scopes

  $scope是一個把view(一個DOM元素)連結到controller上的對象。在咱們的MVC結構裏,這個 $scope 將成爲model,它提供一個綁定到DOM元素(以及其子元素)上的excecution context。

  儘管聽起來有點複雜,但 $scope 實際上就是一個JavaScript對象,controller和view均可以訪問它,因此咱們能夠利用它在二者間傳遞信息。在這個 $scope 對象裏,咱們既存儲數據,又存儲將要運行在view上的函數。

  每個Angular應用都會有一個 $rootScope。這個 $rootScope 是最頂級的scope,它對應着含有 ng-app 指令屬性的那個DOM元素

app.run(function($rootScope) { $rootScope.name = "張三"; });

  若是頁面上沒有明確設定 $scope ,Angular 就會把數據和函數都綁定到這裏。這樣,咱們就能夠在view的任何地方訪問這個name屬性,使用模版表達式{{}},像這樣:{{ name }}  。第一部分中的例子就是靠這一點成功運行的。

 

4.2.2 module

  首先須要明確一下模板的概念。在我還不知道有模板這個東西的時候,曾經用js拼接出很長的HTML字符串,而後append到頁面中,這種方式想一想真是又土又笨。後來又看到能夠把HTML代碼包裹在一個<script>標籤中看成模板,而後按須要取來使用。

  在ng中,模板十分簡單,它就是咱們頁面上的HTML代碼,不須要附加任何額外的東西。在模板中能夠使用各類指令來加強它的功能,這些指令可讓你把模板和數據巧妙的綁定起來。

  在<html>標籤上多了一個屬性ng-app=」MyApp」,它的做用就是用來指定ng的做用域是在<html>標籤之內部分。在js中,咱們調用angular對象的module方法來聲明一個模塊,模塊的名字和ng-app的值對應。這樣聲明一下就可讓ng運行起來了。

  示例:

<html ng-app="demoApp"> var demoApp = angular.module('demoApp', []);

 

4.2.3 ng-controller

  要明確建立一個$scope 對象,咱們就要給DOM元素安上一個controller對象,使用的是ng-controller 指令屬性:

<div ng-controller="MyController"> {{ person.name }} </div>  

  ng-controller指令給所在的DOM元素建立了一個新的$scope 對象,並將這個$scope 對象包含進外層DOM元素的$scope 對象裏。在上面的例子裏,這個外層DOM元素的$scope 對象,就是$rootScope 對象。這個scope鏈是這樣的:

  全部scope都遵循原型繼承(prototypal inheritance),這意味着它們都能訪問父scope們。對任何屬性和方法,若是AngularJS在當前scope上找不到,就會到父 scope上去找,若是在父scope上也沒找到,就會繼續向上回溯,一直到$rootScope 上。即若是controller是多層嵌套的,就會從最裏面一直往外找。惟一的例外:有些指令屬性能夠選擇性地建立一個獨立的scope,讓這個scope不繼承它的父scope們,這個會在指令詳解中說明。

 

4.3 ajax

  $http 服務是AngularJS的核心服務之一,它幫助咱們經過XMLHttpRequest對象或JSONP與遠程HTTP服務進行交流。

  $http 服務是這樣一個函數:它接受一個設置對象,其中指定了如何建立HTTP請求;它將返回一個承諾(*參考JavaScript異步編程的promise模式),其中提供兩個方法: success方法和error方法。

demoApp.controller("demoController", function($http, $scope){ $scope. getAjaxUser = function(){ $http.get({url:"../xxx.action"}).success(function(data){ alert(data); }).error(function(){ Alert(「出錯了!」); }); }; }); 

  AngularJS的AJAX與jquery等框架的AJAX基本一致,這裏就很少說了。

 

4.4表達式

  ng中的表達式與javascript表達式相似可是不能夠劃等號,它是ng本身定義的一套模式。表達式能夠做爲指令的值,如ng-modle=」people.name」、ng-click=」showMe()」,看起來是如此像字符串,故而也叫字符串表達式。也能夠在標記中使用表達式,如{{1+2}},或者與過濾器一塊兒使用{{1+2 | currency}}。在框架內部,字符串不會簡單的使用eval()來執行,而是有一個專門的$parse服務來處理。在ng表達式中不能夠使用循環語句、判斷語句,事實上在模板中使用複雜的表達式也是一個不推薦的作法,這樣視圖與邏輯就混雜在一塊兒了。

  咱們在使用其餘模板庫時,通常都會有模板的循環輸出、分支輸出、邏輯判斷等相似的控制。

  要想理解指令屬性的運做,咱們必須先理解表達式。在以前的例子裏咱們已經見過表達式,例如 {{ user.name }}。請查看例0三、例0四、例05。

  {{ 8 + 1 }} 9

  {{ person }} {"name":"Ari Lerner"}

  {{ 10 * 3.3 | currency }} $33.00

  表達式粗略來看有點像 eval(javascript) 的結果。它們會通過Angular.js的處理,從而擁有如下重要而獨特的性質:

  1.全部表達式都在scope這個context裏被執行,所以能夠使用全部本地 $scope 中的變量。

  2.若是一個表達式的執行致使類型錯誤或引用錯誤,這些錯誤將不會被拋出。

  3.表達式裏不容許任何控制函數流程的功能(如if/else等條件語句)

  4.表達式可接受一個或多個串聯起來的過濾器。

 

4.5過濾器

  過濾器(filter)正如其名,做用就是接收一個輸入,經過某個規則進行處理,而後返回處理後的結果。主要用在數據的格式化上,例如獲取一個數組中的子集,對數組中的元素進行排序等。過濾器一般是伴隨標記來使用的,將你model中的數據格式化爲須要的格式。表單的控制功能主要涉及到數據驗證以及表單控件的加強。ng內置了一些過濾器,它們是:

  currency(貨幣)、date(日期)、filter(子串匹配)、json(格式化json對象)、limitTo(限制個數)、lowercase(小寫)、uppercase(大寫)、number(數字)、orderBy(排序)。

 

4.5.1過濾器使用方式

  總共九種。除此以外還能夠自定義過濾器,這個就強大了,能夠知足任何要求的數據處理。Filter仍是很簡單的,須要明白的是內置的filter如何使用,以及本身如何定義一個filter。

  filter的兩種使用方法:

  1. 在模板中使用filter

  咱們能夠直接在{{}}中使用filter,跟在表達式後面用 | 分割,語法以下:

{{ expression | filter }} 

 

  也能夠多個filter連用,上一個filter的輸出將做爲下一個filter的輸入:

{{ expression | filter1 | filter2 | ... }}

  

  filter能夠接收參數,參數用 : 進行分割,以下:

{{ expression | filter:argument1:argument2:... }}

  

  除了對{{}}中的數據進行格式化,咱們還能夠在指令中使用filter,例如先對數組array進行過濾處理,而後再循環輸出:

 

<span ng-repeat="a in array | filter "> 

 

  2. 在controller和service中使用filter

  咱們的js代碼中也能夠使用過濾器,方式就是咱們熟悉的依賴注入,例如我要在controller中使用currency過濾器,只需將它注入到該controller中便可,代碼以下:

app.controller('testC',function($scope,currencyFilter){

    $scope.num = currencyFilter(123534);  

} 

  在模板中使用{{num}}就能夠直接輸出$123,534.00了!在服務中使用filter也是一樣的道理。

  若是你要在controller中使用多個filter,並不須要一個一個注入,ng提供了一個$filter服務能夠來調用所需的filter,你只需注入一個$filter就夠了,使用方法以下:

app.controller('testC',function($scope,$filter){

    $scope.num = $filter('currency')(123534);  

    $scope.date = $filter('date')(new Date());  

}

  能夠達到一樣的效果。好處是你能夠方便使用不一樣的filter了。

 

4.5.2 ng的內置過濾器

  ng內置了九種過濾器,使用方法都很是簡單,看文檔即懂。不過爲了之後不去翻它的文檔,我在這裏仍是作一個詳細的記錄。

  currency(貨幣)、date(日期)、filter(子串匹配)、json(格式化json對象)、limitTo(限制個數)、lowercase(小寫)、uppercase(大寫)、number(數字)、orderBy(排序)

1. currency (貨幣處理)

  使用currency能夠將數字格式化爲貨幣,默認是美圓符號,你能夠本身傳入所需的符號,例如我傳入人民幣:

  {{num | currency : '¥'}}  

2. date (日期格式化)

  原生的js對日期的格式化能力有限,ng提供的date過濾器基本能夠知足通常的格式化要求。用法以下:

  {{date | date : 'yyyy-MM-dd hh:mm:ss EEEE'}}  

  參數用來指定所要的格式,y M d h m s E 分別表示 年 月 日 時 分 秒 星期,你能夠自由組合它們。也能夠使用不一樣的個數來限制格式化的位數。另外參數也能夠使用特定的描述性字符串,例如「shortTime」將會把時間格式爲12:05 pm這樣的。ng提供了八種描述性的字符串,我的以爲這些有點多餘,我徹底能夠根據本身的意願組合出想要的格式,不肯意去記這麼多單詞~

3. filter(匹配子串)

  這個名叫filter的filter。用來處理一個數組,而後能夠過濾出含有某個子串的元素,做爲一個子數組來返回。能夠是字符串數組,也能夠是對象數組。若是是對象數組,能夠匹配屬性的值。它接收一個參數,用來定義子串的匹配規則。下面舉個例子說明一下參數的用法,我用如今特別火的幾個孩子定義了一個數組:

$scope.childrenArray = [
    {name:'kimi',age:3},
    {name:'cindy',age:4},
    {name:'anglar',age:4},
    {name:'shitou',age:6},
    {name:'tiantian',age:5}
];

$scope.func = function(e){
  return e.age>4;
}

{{ childrenArray | filter : 'a' }} //匹配屬性值中含有a的 {{ childrenArray | filter : 4 }} //匹配屬性值中含有4的 {{ childrenArray | filter : {name : 'i'} }} //參數是對象,匹配name屬性中含有i的 {{childrenArray | filter : func }} //參數是函數,指定返回age>4的  

4. json(格式化json對象)

  json過濾器能夠把一個js對象格式化爲json字符串,沒有參數。這東西有什麼用呢,我通常也不會在頁面上輸出一個json串啊,官網說它能夠用來進行調試,嗯,是個不錯的選擇。或者,也能夠用在js中使用,做用就和咱們熟悉的JSON.stringify()同樣。用法超級簡單:

  {{ jsonTest | json}}

5. limitTo(限制數組長度或字符串長度)

  limitTo過濾器用來截取數組或字符串,接收一個參數用來指定截取的長度,若是參數是負值,則從數組尾部開始截取。我的以爲這個filter有點雞肋,首先只能從數組或字符串的開頭/尾部進行截取,其次,js原生的函數就能夠代替它了,看看怎麼用吧:

  {{ childrenArray | limitTo : 2 }}  //將會顯示數組中的前兩項  

6. lowercase(小寫)

  把數據轉化爲所有小寫。太簡單了,很少解釋。一樣是很雞肋的一個filter,沒有參數,只能把整個字符串變爲小寫,不能指定字母。怎麼用我都懶得寫了。

7. uppercase(大寫)

  同上。

8. number(格式化數字)

  number過濾器能夠爲一個數字加上千位分割,像這樣,123,456,789。同時接收一個參數,能夠指定float類型保留幾位小數:

  {{ num | number : 2 }}  

9. orderBy(排序)

  orderBy過濾器能夠將一個數組中的元素進行排序,接收一個參數來指定排序規則,參數能夠是一個字符串,表示以該屬性名稱進行排序。能夠是一個函數,定義排序屬性。還能夠是一個數組,表示依次按數組中的屬性值進行排序(若按第一項比較的值相等,再按第二項比較),仍是拿上面的孩子數組舉例:

<div>{{ childrenArray | orderBy : 'age' }}</div>      //按age屬性值進行排序,如果-age,則倒序

<div>{{ childrenArray | orderBy : orderFunc }}</div>   //按照函數的返回值進行排序

<div>{{ childrenArray | orderBy : ['age','name'] }}</div>  //若是age相同,按照name進行排序 

  內置的過濾器介紹完了,正如你所看到的,ng內置的過濾器也並非萬能的,事實上好多都比較雞肋。更個性化的需求就須要咱們來定義本身的過濾器了,下面來看看如何自定義過濾器。

 

4.5.3自定義過濾器及示例

  filter的自定義方式也很簡單,使用module的filter方法,返回一個函數,該函數接收輸入值,並返回處理後的結果。話很少說,咱們來寫一個看看。好比我須要一個過濾器,它能夠返回一個數組中下標爲奇數的元素,代碼以下:

app.filter('odditems',function(){

    return function(inputArray){
        var array = [];
        for(var i=0;i<inputArray.length;i++){
            if(i%2!==0){
                array.push(inputArray[i]);
            }
        }
        return array;
    }
});

  格式就是這樣,你的處理邏輯就寫在內部的那個閉包函數中。你也可讓本身的過濾器接收參數,參數就定義在return的那個函數中,做爲第二個參數,或者更多個參數也能夠。

  自定義過濾器實例(例04):

/* View html */
First name:<input ng-model="user.firstName"/><br/>
Last  name:<input ng-model="user.lastName"/> <br/>
First name:{{user.firstName}}      
Last name:{{user.lastName}}
<br/> Fullname:{{user | flFullname}}<br/> Fullname:{{user | flFullname:"-"}}<br/> Fullname:{{user | flFullname:"•" | uppercase }} /* Controller js */ demoApp.filter("flFullname", function() { return function(user, sep) { sep = sep || " "; user = user || {}; fullName = ""; if(user.firstName){fullName += user.firstName;} if(user.lastName){fullName = fullName + sep + user.lastName;} if(fullName && fullName.length>0){return fullName; }else{return "";} }; });

 

4.6指令(directive)

  經過使用模板,咱們能夠把model和controller中的數據組裝起來呈現給瀏覽器,還能夠經過數據綁定,實時更新視圖,讓咱們的頁面變成動態的。

  模板中能夠使用的東西包括如下四種:

  1.指令(directive):ng提供的或者自定義的標籤和屬性,用來加強HTML表現力;

  2.標記(markup):即雙大括號{{}},可將數據單向綁定到HTML中;

  3.過濾器(filter):用來格式化輸出數據;

  4.表單控制:用來加強表單的驗證功能。

  其中,指令無疑是使用量最大的,ng內置了不少指令用來控制模板,如ng-repeat,ng-class,也有不少指令來幫你完成業務邏輯,如ng-controller,ng-model。

指令的幾種使用方式以下:

  1.做爲標籤:<my-dir></my-dir>

  2.做爲屬性:<span my-dir="exp"></span>

  3.做爲註釋:<!-- directive: my-dir exp -->

  4.做爲類名:<span class="my-dir: exp;"></span>

其實經常使用的就是做爲標籤和屬性。

 

4.6.1樣式相關的指令

  既然模板就是普通的HTML,那我首要關心的就是樣式的控制,元素的定位、字體、背景色等等如何能夠靈活控制。下面來看看經常使用的樣式控制指令。

1. ng-class

  ng-class用來給元素綁定類名,其表達式的返回值能夠是如下三種:

  1.類名字符串,能夠用空格分割多個類名,如’redtext boldtext’;

  2.類名數組,數組中的每一項都會層疊起來生效;

  3.一個名值對應的map,其鍵值爲類名,值爲boolean類型,當值爲true時,該類會被加在元素上。

 

  下面來看一個使用map的例子:

  ng-class測試

  紅色 加粗 刪除線 

  map:{redtext:{{red}}, boldtext:{{bold}}, striketext:{{strike}}}

  若是你想拼接一個類名出來,能夠使用插值表達式,如:

  <div class=」{{style}}text」>字體樣式測試</div>

  而後在controller中指定style的值:

  $scope.style = ‘red’;

  注意我用了class而不是ng-class,這是不能夠對換的,官方的文檔也未作說明,姑且認爲這是ng的語法規則吧。

  與ng-class相近的,ng還提供了ng-class-odd、ng-class-even兩個指令,用來配合ng-repeat分別在奇數列和偶數列使用對應的類。這個用來在表格中實現隔行換色再方便不過了。

2. ng-style

  ng-style用來綁定元素的css樣式,其表達式的返回值爲一個js對象,鍵爲css樣式名,值爲該樣式對應的合法取值。用法比較簡單:

<div ng-style="{color:'red'}">ng-style測試</div>

<div ng-style="style">ng-style測試</div>

$scope.style = {color:'red'}; 

 

3. ng-show,ng-hide

   對於比較經常使用的元素顯隱控制,ng也作了封裝,ng-show和ng-hide的值爲boolean類型的表達式,當值爲true時,對應的show或hide生效。框架會用display:block和display:none來控制元素的顯隱。

 

4.6.2表單控件功能相關指令

  對於經常使用的表單控件功能,ng也作了封裝,方便靈活控制。

  ng-checked控制radio和checkbox的選中狀態

  ng-selected控制下拉框的選中狀態

  ng-disabled控制失效狀態

  ng-multiple控制多選

  ng-readonly控制只讀狀態

  以上指令的取值均爲boolean類型,當值爲true時相關狀態生效,道理比較簡單就很少作解釋。注意: 上面的這些只是單向綁定,即只是從數據到模板,不能副作用於數據。要雙向綁定,仍是要使用 ng-model 。

 

4.6.3事件綁定相關指令

事件綁定是javascrpt中比較重要的一部份內容,ng對此也作了詳細的封裝,正如咱們以前使用過的ng-click同樣,事件的指令以下:

  ng-click

  ng-change

  ng-dblclick

  ng-mousedown

  ng-mouseenter

  ng-mouseleave

  ng-mousemove

  ng-mouseover

  ng-mouseup

  ng-submit

  事件綁定指令的取值爲函數,而且須要加上括號,例如:

<select ng-change=」change($event)」></select>

  

  而後在controller中定義以下:

$scope.change = function($event){

         alert($event.target);

         //……………………
} 

  在模板中能夠用變量$event將事件對象傳遞到controller中。

  對於ng的這種設計,一些人有所質疑,視圖與事件綁定混在一塊兒到底好很差?咱們不是要講究視圖與邏輯分離嗎?如此一來,把事件的綁定又變回了內聯的,豈不是歷史的倒退。我也同樣對此表示不解,由於不寫onclick已經不少年。。。但既然已經存在了,咱們不妨往合理的方向上想想,或許ng的設計者壓根就不想讓模板成爲單純的視圖層,原本就是想加強HTML,讓它有一點業務能力。這麼想的話彷佛也能想通,好吧,先欺騙一下本身吧~

 

4.6.4特殊的ng-src和ng-href

在說明這兩個指令的特殊以前,須要先了解一下ng的啓動及執行過程:

  1) 瀏覽器加載靜態HTML文件並解析爲DOM;

  2) 瀏覽器加載angular.js文件;

  3) angular監聽DOMContentLoaded 事件,監聽到時開始啓動;

  4) angular尋找ng-app指令,肯定做用範圍;

  5) 找到app中定義的Module使用$injector服務進行依賴注入;

  6) 根據$injector服務建立$compile服務用於編譯;

  7) $compile服務編譯DOM中的指令、過濾器等;

  8) 使用ng-init指令,將做用域中的變量進行替換;

  9) 最後生成了咱們在最終視圖。

  能夠看到,ng框架是在DOMcontent加載完畢後纔開始發揮做用。假如咱們模板中有一張圖片以下:

  <img src=」{{imgUrl}}」 />

  那麼在頁面開始加載到ng編譯完成以前,頁面上會一直顯示一張錯誤的圖片,由於路徑{{imgUrl}}還未被替換。

  爲了不這種狀況,咱們使用ng-src指令,這樣在路徑被正確獲得以前就不會顯示找不到圖片。同理,<a>標籤的href屬性也須要換成ng-href,這樣頁面上就不會先出現一個地址錯誤的連接。

  順着這個思路再多想一點,咱們在模板中使用{{}}顯示數據時,在ng編譯完成以前頁面上豈不是會顯示出大括號及裏面的表達式?確實是這樣。爲了不這個,ng中有一個與{{}}等同的指令:ng-bind,一樣用於單向綁定,在頁面剛加載的時候就不會顯示出對用戶無用的數據了。儘管這樣你可能不但沒舒心反而更糾結了,{{}}那麼好用易理解,還不能用了不成?好消息是咱們依然能夠使用。由於我編寫的是單頁面應用,頁面只會在加載index.html的時

候出這個問題,只需在index.html中的模板中換成ng-bind就行。其餘的模板是咱們動態加載的,就能夠放心使用{{}}了

 

4.6.5 自定義指令示例

  下面咱們來解析下指令的例子(例07)。

  1.首先,咱們定義一個名爲userInfo的指令:

demoApp.directive('userInfo',function(){

    return {
        restrict : 'E',
        templateUrl : 'userInfoTemplate.html',
        replace : true,
        transclude : true,
        scope : {
            mytitle : '=etitle'
        },
        link : function(scope,element,attrs){
            scope.showText = false;
            scope.toggleText = function(){
                scope.showText = ! scope.showText;
            }
        }
    };
}) 

  Restrict爲'E':用做標籤;

  replace爲true:用模板替換當前標籤;

  transclude爲true:將當前元素的內容轉移到模板中;

  scope 爲 {mytitle : '=etitle'}:定義一個名爲mytitle的MODEL,其值指向當前元素的etitle屬性;

  templateUrl爲'userInfoTemplate.html':模板內容爲ng-template定義ID爲userInfoTemplate.html的內容;

  link:指定所包含的行爲。其具體的說明及其餘參數,請參考:6.2指令詳解。

 

  2. userInfoTemplate.html模板爲:

<script type="text/ng-template" id="userInfoTemplate.html">
    <div class="mybox">
        <div class="mytitle" style="cursor: pointer;" ng-click="toggleText()">
            { {mytitle} }
        </div>

        <div ng-transclude ng-show="showText">
        </div>
    </div>
</script>

  將當前元素的內容添加到有ng-transclude屬性的這個DIV下,默認是隱藏的。

 

  3.Controller信息:

demoApp.controller("test7Controller", function($scope) {

    $scope.title = '我的簡介';

    $scope.text = '你們好,我正在研究AngularJs,歡迎你們與我交流。';

    $scope.updateInfo = function() {

        $scope.title = '我的信息';

        $scope.text = '你們好,今每天氣真好!';

    }

});

 

  4.指令使用方式(View信息)爲:

<user-info etitle="title">{ {text} }</user-info>

 

  Etitle指向Controller中的$scope.title。注意命名方式:指令名爲userInfo,對應的標籤爲user-info。

 

4.7服務(service)

4.7.1服務介紹

  服務這個概念其實並不陌生,在其餘語言中如java便有這樣的概念,其做用就是對外提供某個特定的功能,如消息服務,文件壓縮服務等,是一個獨立的模塊。ng的服務是這樣定義的:Angular services are singletons objects or functions that carry out specific tasks common to web apps.

  它是一個單例對象或函數,對外提供特定的功能。

  首先是一個單例,即不管這個服務被注入到任何地方,對象始終只有一個實例。

  其次這與咱們本身定義一個function而後在其餘地方調用不一樣,由於服務被定義在一個模塊中,因此其使用範圍是能夠被咱們管理的。ng的避免全局變量污染意識很是強。

  ng提供了不少內置的服務,能夠到API中查看http://docs.angularjs.org/api/

  知道了概念,咱們來拉一個service出來溜溜,看看究竟是個什麼用法。咱們在controller中直接聲明$location服務,這依靠ng的依賴注入機制。$location提供地址欄相關的服務,咱們在此只是簡單的獲取當前的地址。

  服務的使用是如此簡單,咱們能夠把服務注入到controller、指令或者是其餘服務中。

 

4.7.2自定義服務

  如同指令同樣,系統內置的服務以$開頭,咱們也能夠本身定義一個服務。定義服務的方式有以下幾種:

  1.使用系統內置的$provide服務;

  2.使用Module的factory方法;

  3.使用Module的service方法。

  下面經過一個小例子來分別試驗一下。咱們定義一個名爲remoteData服務,它能夠從遠程獲取數據,這也是咱們在程序中常用的功能。不過我這裏沒有遠程服務器,就寫死一點數據模擬一下。

//使用$provide來定義

var app = angular.module('MyApp', [], function($provide) {

    $provide.factory('remoteData', function() {

        var data = {
            name: 'n',
            value: 'v'
        };

        return data;

    });

});

//使用factory方法

app.factory('remoteData', function() {

    var data = {
        name: 'n',
        value: 'v'
    };

    return data;

});

//使用service方法

app.service('remoteData', function() {

    this.name = 'n';

    this.value = 'v';

});

  Module的factory和$provide的factory方法是如出一轍的,從官網文檔看它們其實就是一回事。至於Module內部是如何調用的,我此處並不打算深究,我只要知道怎麼用就行了。

  再看Module的service方法,它沒有return任何東西,是由於service方法自己返回一個構造器,系統會自動使用new關鍵字來建立出一個對象。因此咱們看到在構造器函數內能夠使用this,這樣調用該服務的地方即可以直接經過remoteData.name來訪問數據了。

 

4.7.3管理服務的依賴關係

  服務與服務中間能夠有依賴關係,例如咱們這裏定義一個名爲validate的服務,它的做用是驗證數據是否合法,它須要依賴咱們從遠程獲取數據的服remoteData。代碼以下:

   在factory的參數中,咱們能夠直接傳入服務remoteData,ng的依賴注入機制便幫咱們作好了其餘工做。不過必定要保證這個參數的名稱與服務名稱一致,ng是根據名稱來識別的。若參數的名次與服務名稱不一致,你就必須顯示的聲明一下,方式以下:

app.factory('validate', ['remoteData', function(remoteDataService) {

    return function() {

        if(remoteDataService.name == 'n') {

            alert('驗證經過');

        }

    };

}]);  

 

  咱們在controller中注入服務也是一樣的道理,使用的名稱須要與服務名稱一致才能夠正確注入。不然,你必須使用$inject來手動指定注入的服務。好比:

function testC(scope, rd) {

    scope.getData = function() {

        alert('name:' + rd.name + '   value:' + rd.value);

    }

}

testC.$inject = ['$scope', 'remoteData'];

 

  在controller中注入服務,也能夠在定義controller時使用數組做爲第二個參數,在此處把服務注入進去,這樣在函數體中使用不一致的服務名稱也是能夠的,不過要確保注入的順序是一致的,如:

app.controller('testC', ['$scope', 'remoteData', function($scope, rd) {

    $scope.getData = function() {

        alert('name:' + rd.name + '   value:' + rd.value);

    }

}]);

 

4.7.4 自定義服務示例

  接下來讓咱們看下例子(例08 自定義服務)代碼,自定義userService服務:

demoApp.factory('userService', ['$http', function($http) {

    var doGetUser = function(userId, path) {

        //return $http({

        //method: 'JSONP',

        //url: path

        //});

        /*手動指定數據*/

        var data = {
            userId: "woshishui",
            userName: "我是誰",
            userInfo: "我是誰!我是誰!"
        };;

        if(userId == 'zhangsan') {

            data = {
                userId: "zhangsan",
                userName: "張三",
                userInfo: "我是張三,我爲本身"
            };

        } else if(userId == 'lisi') {

            data = {
                userId: "lisi",
                userName: "李四",
                userInfo: "我是李四,我爲卿狂!"
            };

        }

        return data;

    }

    return {

        /*userService對外暴露的函數,可有多個*/

        getUser: function(userId) {

            return doGetUser(userId, '../xxx/xxx.action');

        }

    };

}]);

 

  咱們建立了一個只有一個方法的userService,getUser爲這個服務從後臺獲取用戶信息的函數,而且對外暴露。固然,因爲這是一個靜態的例子,沒法訪問後臺,那麼咱們便制定其返回的數據。

  而後咱們把這個服務添加到咱們的controller中。咱們創建一個controller並加載(或者注入)userService做爲運行時依賴,咱們把service的名字做爲參數傳遞給controller 函數:

demoApp.controller("test8Controller", function($scope, userService) {

    /*文章信息*/

    $scope.articles = [{

        title: "愛飛像風",

        userId: "zhangsan",

        userName: "張三"

    }, {

        title: "沒法中止的雨",

        userId: "lisi",

        userName: "李四"

    }];

    $scope.showUserInfo = false; //顯示做者詳細信息開關

    $scope.currentUser = {}; //當前選中的做者

    $scope.getUserInfo = function(userId) {

        $scope.currentUser = userService.getUser(userId);

        //調用 userService的getUser函數

        $scope.showUserInfo = true;

        setTimeout(function() { //定時器:隱藏做者詳細信息

            $scope.showUserInfo = false;

        }, 3000);

    }

});

 

  咱們的userService注入到咱們的test8Controller後,咱們就能夠像使用其餘服務(咱們前面提到的$http服務)同樣的使用userService了。相關的HTML代碼以下:

/* View HTML*/

<tr ng-repeat="article_ in articles">

    <td>

        {{article_.title}}

    </td>

    <td>

        <a href="javascript:void(0);" ng-click="getUserInfo(article_.userId)"> {{article_.userName}} </a>

    </td>

</tr>

......

<div ng-show="showUserInfo">

    用戶ID:{{currentUser.userId}}<br/> 用戶名:{{currentUser.userName}}

    <br/> 用戶簡介:{{currentUser.userInfo}}

    <br/>

</div>

 

4.8依賴注入DI

  經過依賴注入,ng想要推崇一種聲明式的開發方式,即當咱們須要使用某一模塊或服務時,不須要關心此模塊內部如何實現,只需聲明一下就能夠使用了。在多處使用只需進行屢次聲明,大大提升可複用性。

  好比咱們的controller,在定義的時候用到一個$scope參數。

app.controller('testC',function($scope){}); 

  若是咱們在此處還需操做其餘的東西,好比與瀏覽器地址欄進行交互。咱們只需再多添一個參數$location進去:

app.controller('testC',function($scope,$location){}); 

  這樣即可以經過$location來與地址欄進行交互了,咱們僅僅是聲明瞭一下,所需的其餘代碼,框架已經幫咱們注入了。咱們很明顯的感受到了這個函數已經不是常規意義上的javascript函數了,在常規的函數中,把形參換一個名字照樣能夠運行,但在此處如果把$scope換成別的名字,程序便不能運行了。由於這是已經定義好的服務名稱。

  這即是依賴注入機制。瓜熟蒂落的推斷,咱們能夠本身定義模塊和服務,而後在須要的地方進行聲明,由框架來替咱們注入。

  來看下咱們如何定義一個服務:

app.factory('tpls',function(){

    return ['tpl1','tpl2','tpl3','tpl4'];

});  

  看上去至關簡單,是由於我在這裏僅僅是直接返回一個數組。在實際應用中,這裏應該是須要向服務器發起一個請求,來獲取到這些模板們。服務的定義方式有好幾種,包括使用provider方法、使用factory方法,使用service方法。它們之間的區別暫且不關心。咱們如今只要能建立一個服務出來就能夠了。我使用了factory方法。一個須要注意的地方是,框架提供的服務名字都是由$開頭的,因此咱們本身定義的最好不要用$開頭,防止發生命名衝突

  定義好一個服務後,咱們就能夠在控制器中聲明使用了,以下:

app.controller('testC', function($scope, tpls) {

    $scope.question = questionModel;

    $scope.nowTime = new Date().valueOf();

    $scope.templates = tpls; //賦值到$scope中

    $scope.addOption = function() {

        var o = {
            content: ''
        };

        $scope.question.options.push(o);

    };

    $scope.delOption = function(index) {

        $scope.question.options.splice(index, 1);

    };

});  

  此時,若在模板中書寫以下代碼,咱們即可以獲取到服務tpls所提供的數據了:

<a href="javascript:void(0);" ng-repeat="t in templates">{{t}} </a><br />

 

4.9路由(route)

  在談路由機制前有必要先提一下如今比較流行的單頁面應用,就是所謂的single page APP。爲了實現無刷新的視圖切換,咱們一般會用ajax請求從後臺取數據,而後套上HTML模板渲染在頁面上,然而ajax的一個致命缺點就是致使瀏覽器後退按鈕失效,儘管咱們能夠在頁面上放一個大大的返回按鈕,讓用戶點擊返回來導航,但老是沒法避免用戶習慣性的點後退。解決此問題的一個方法是使用hash,監聽hashchange事件來進行視圖切換,另外一個方法是用HTML5的history API,經過pushState()記錄操做歷史,監聽popstate事件來進行視圖切換,也有人把這叫pjax技術。

  如此一來,便造成了經過地址欄進行導航的深度連接(deeplinking ),也就是咱們所須要的路由機制。經過路由機制,一個單頁應用的各個視圖就能夠很好的組織起來了。

4.9.1 ngRoute內容

  ng的路由機制是靠ngRoute提供的,經過hash和history兩種方式實現了路由,能夠檢測瀏覽器是否支持history來靈活調用相應的方式。ng的路由(ngRoute)是一個單獨的模塊,包含如下內容:

  1.服務$routeProvider用來定義一個路由表,即地址欄與視圖模板的映射

  2.服務$routeParams保存了地址欄中的參數,例如{id : 1, name : 'tom'}

  3.服務$route完成路由匹配,而且提供路由相關的屬性訪問及事件,如訪問當前路由對應的controller

  4.指令ngView用來在主視圖中指定加載子視圖的區域

  以上內容再加上$location服務,咱們就能夠實現一個單頁面應用了。下面來看一下具體如何使用這些內容。

4.9.2 ng的路由機制

  第一步:引入文件和依賴

  ngRoute模塊包含在一個單獨的文件中,因此第一步須要在頁面上引入這個文件,以下:

<script src="http://code.angularjs.org/1.2.8/angular.min.js"></script>

<script src="http://code.angularjs.org/1.2.8/angular-route.min.js"></script>

  光引入還不夠,咱們還需在模塊聲明中注入對ngRoute的依賴,以下:

var app = angular.module('MyApp', ['ngRoute']);  

 

  完成了這些,咱們就能夠在模板或是controller中使用上面的服務和指令了。下面咱們須要定義一個路由表。

  第二步:定義路由表

  $routeProvider提供了定義路由表的服務,它有兩個核心方法,when(path,route)和otherwise(params),先看一下核心中的核心when(path,route)方法。

  when(path,route)方法接收兩個參數,path是一個string類型,表示該條路由規則所匹配的路徑,它將與地址欄的內容($location.path)值進行匹配。若是須要匹配參數,能夠在path中使用冒號加名稱的方式,如:path爲/show/:name,若是地址欄是/show/tom,那麼參數name和所對應的值tom便會被保存在$routeParams中,像這樣:{name : tom}。咱們也能夠用*進行模糊匹配,如:/show*/:name將匹配/showInfo/tom。

  route參數是一個object,用來指定當path匹配後所需的一系列配置項,包括如下內容:

1) controller //function或string類型。在當前模板上執行的controller函數,生成新的scope;

2) controllerAs //string類型,爲controller指定別名;

3) template //string或function類型,視圖z所用的模板,這部份內容將被ngView引用;

4) templateUrl //string或function類型,當視圖模板爲單獨的html文件或是使用了<script type="text/ng-template">定義模板時使用;

5) resolve //指定當前controller所依賴的其餘模塊;

6) redirectTo //重定向的地址。

 

最簡單狀況,咱們定義一個html文件爲模板,並初始化一個指定的controller:

function emailRouteConfig($routeProvider) {

    $routeProvider.when('/show', {

        controller: ShowController,

        templateUrl: 'show.html'

    }).

    when('/put/:name', {

        controller: PutController,

        templateUrl: 'put.html'

    });

};  

  otherwise(params)方法對應路徑匹配不到時的狀況,這時候咱們能夠配置一個redirectTo參數,讓它重定向到404頁面或者是首頁。

 

  第三步:在主視圖模板中指定加載子視圖的位置

  咱們的單頁面程序都是局部刷新的,那這個「局部」是哪裏呢,這就輪到ngView出馬了,只需在模板中簡單的使用此指令,在哪裏用,哪裏就是「局部」。例如:

<div ng-view></div>  或:<ng-view></ng-view>  

 

  咱們的子視圖將會在此處被引入進來。完成這三步後,你的程序的路由就配置好了。

 

4.9.3 路由示例

  下面咱們將用一個例子(例09)來講明路由的使用方式及步驟:

  1.爲demoApp添加一個路由,代碼以下:

demoApp.config(['$routeProvider', function($routeProvider) {

    $routeProvider.when('/list', {

        templateUrl: 'route/list.html',

        controller: 'routeListController'

    }).when('/list/:id', {

        templateUrl: 'route/detail.html',

        controller: 'routeDetailController'

    }).otherwise({

        redirectTo: '/list'

    });

}]);

  /list 對應爲:route/list.html頁面,顯示用戶列表;/list/:id對應於route/detail.html頁面,顯示用戶詳細信息。

 

  2.爲list.html和detail.html分別聲明Controller:routeListController和routeDetailController。

demoApp.controller('routeListController', function($scope) {

    $scope.users = [{
            userId: "zhangsan",
            userName: "張三",
            userInfo: "我是張三,我爲本身帶鹽!"
        },

        {
            userId: "lisi",
            userName: "李四",
            userInfo: "我是李四,我爲卿狂!"
        },

        {
            userId: "woshishui",
            userName: "我是誰",
            userInfo: "我是誰!我是誰!我是誰!"
        }
    ];

});

demoApp.controller('routeDetailController', function($scope, $routeParams, userService) {

    $scope.userDetail = userService.getUser($routeParams.id);

});

  routeDetailController中如上面提到的同樣,注入了userService服務,在這裏直接拿來用。

 

  3.建立list.html和detail.html頁面,代碼以下:

<hr/>

<h3>Route : List.html(用戶列表頁面)</h3>

<ul>

    <li ng-repeat="user in users">

        <a href="#/list/{{ user.userId }}"> {{ user.userName }}</a>

    </li>

</ul>

<hr/>

<h3>Route : detail.html(用戶詳細信息頁面)</h3>

<h3>用戶名:<span style="color: red;">{{userDetail.userName}}</span></h3>

<div>

    <span>用戶ID:{{userDetail.userId}}</span><span>用戶名:{{userDetail.userName}}</span>

</div>

<div>

    用戶簡介:<span>{{userDetail.userInfo}}</span>

</div>

<div>

    <a href="#/list">返回</a>

</div>

 

  4. 路由局部刷新位置:

<h1>AngularJS路由(Route) 示例</h1>  

<div ng-view></div>

 

4.10 NG動畫效果

4.10.1 NG動畫效果簡介

  NG動畫效果,如今能夠經過CSS3或者是JS來實現,若是是經過JS來實現的話,須要其餘JS庫(好比JQuery)來支持,實際上底層實現仍是靠其餘JS庫,只是NG將其封裝了,使其更易使用。

  NG動畫效果包含如下幾種:

  • enter:元素添加到DOM中時執行動畫;
  • leave:元素從DOM刪除時執行動畫;
  • move:移動元素時執行動畫;
  • beforeAddClass:在給元素添加CLASS以前執行動畫;
  • addClass:在給元素添加CLASS時執行動畫;
  • beforeRemoveClass:在給元素刪除CLASS以前執行動畫;
  • removeClass:在給元素刪除CLASS時執行動畫。

其相關參數爲:

var ngModule = angular.module('YourApp', ['ngAnimate']);

demoApp.animation('.my-crazy-animation', function() {

    return {

        enter: function(element, done) {

            //run the animation here and call done when the animation is complete

            return function(cancelled) {

                //this (optional) function will be called when the animation

                //completes or when the animation is cancelled (the cancelled

                //flag will be set to true if cancelled).

            };

        },

        leave: function(element, done) {},

        move: function(element, done) {},

        //animation that can be triggered before the class is added

        beforeAddClass: function(element, className, done) {},

        //animation that can be triggered after the class is added

        addClass: function(element, className, done) {},

        //animation that can be triggered before the class is removed

        beforeRemoveClass: function(element, className, done) {},

        //animation that can be triggered after the class is removed

        removeClass: function(element, className, done) {}

    };

});

 

4.10.2 動畫效果示例

  下面咱們來看下DEMO中的例子(例10)。

  1.首先,咱們在demoApp下定義一個動畫效果,匹配CLASS:」 .border-animation」

/*定義動畫*/

demoApp.animation('.border-animation', function() {

    return {

        beforeAddClass: function(element, className, done) {

            $(element).stop().animate({

                'border-width': 1

            }, 2000, function() {

                done();

            });

        },

        removeClass: function(element, className, done) {

            $(element).stop().animate({

                'border-width': 50

            }, 3000, function() {

                done();

            });

        }

    };

});

  動畫效果的含義就是:在匹配CLASS爲border-animation的元素添加一個CLASS以前使其邊框的寬度在2秒內變爲1PX;並在其移除一個CLASS時使其邊框的寬度在3秒內變爲50PX。

 

  2. 視圖中的代碼以下(主要,其餘相關樣式請查看例子代碼):

<div class="border-animation" ng-show="testShow"></div>

<a href="javascript:void(0);" ng-click="testShow=!testShow" >Change</a>

  ng-show爲false時會爲其加上「ng-hide「的CLASS; ng-show爲true時會爲其移除「ng-hide「的CLASS,從而觸發動畫效果。

 

  3.其餘代碼:

demoApp.controller("test10Controller", function($scope, $animate) {

    $scope.testShow = true;

});

 

5 功能演示

  略

 

6 AngularJS進階

6.1數據綁定原理研究

  Angular用戶都想知道數據綁定是怎麼實現的。你可能會看到各類各樣的詞彙:$watch、$apply、$digest、dirty-checking...它們是什麼?它們是如何工做的呢?這裏我想回答這些問題,其實它們在官方的文檔裏都已經回答了,可是我仍是想把它們結合在一塊兒來說,可是我只是用一種簡單的方法來說解,若是要想了解技術細節,查看源代碼。

6.1.1 AngularJS擴展事件循環

  咱們的瀏覽器一直在等待事件,好比用戶交互。假如你點擊一個按鈕或者在輸入框裏輸入東西,事件的回調函數就會在javascript解釋器裏執行,而後你就能夠作任何DOM操做,等回調函數執行完畢時,瀏覽器就會相應地對DOM作出變化。(記住,這是個重要的概念),爲了解釋什麼是context以及它如何工做,咱們還須要解釋更多的概念。

6.1.2 $watch 隊列

  每次你綁定一些東西到你的DOM上時你就會往$watch隊列裏插入一條$watch。想象一下$watch就是那個能夠檢測它監視的model裏時候有變化的東西。例如你有以下的代碼:

/*View  index.html */

User: <input type="text" ng-model="user" />

Password: <input type="password" ng-model="pass" />

  在這裏咱們有個$scope.user,他被綁定在了第一個輸入框上,還有個$scope.pass,它被綁定在了第二個輸入框上,而後咱們在$watch list裏面加入兩個$watch。

  再看下面的例子:

/*Controller  controllers.js */

app.controller('MainCtrl', function($scope) {

   $scope.foo = "Foo";

   $scope.world = "World";

});

/*View  index.html */

Hello, {{ World }}

  這裏,即使咱們在$scope上添加了兩個東西,可是隻有一個綁定在了DOM上,所以在這裏只生成了一個$watch。

  再看下面的例子:

/*Controller  controllers.js */

app.controller('MainCtrl', function($scope) {

  $scope.people = [...];

});

/*View  index.html */

<ul>

  <li ng-repeat="person in people">

      {{person.name}} - {{person.age}}

  </li>

</ul>

  這裏又生成了多少個$watch呢?每一個person有兩個(一個name,一個age),而後ng-repeat又有一個,所以10個person一共是(2 * 10) +1,也就是說有21個$watch。 

  所以,每個綁定到了DOM上的數據都會生成一個$watch

  那這些$watch是何時生成的呢? 

  當咱們的模版加載完畢時,也就是在linking階段(Angular分爲compile階段和linking階段),Angular解釋器會尋找每一個directive,而後生成每一個須要的$watch。

 

6.1.3 $digest循環

  還記得我前面提到的擴展的事件循環嗎?當瀏覽器接收到能夠被angular context處理的事件時,$digest循環就會觸發。這個循環是由兩個更小的循環組合起來的。一個處理evalAsync隊列,另外一個處理$watch隊列。 這個是處理什麼的呢?$digest將會遍歷咱們的$watch,而後詢問:

•嘿,$watch,你的值是什麼? 

◦是9。

•好的,它改變過嗎? 

◦沒有,先生。

•(這個變量沒變過,那下一個)

•你呢,你的值是多少? 

◦報告,是Foo。

•剛纔改變過沒? 

◦改變過,剛纔是Bar。

•(很好,咱們有DOM須要更新了)

•繼續詢問直到$watch隊列都檢查過。

  這就是所謂的dirty-checking。既然全部的$watch都檢查完了,那就要問了:有沒有$watch更新過?若是有至少一個更新過,這個循環就會再次觸發,直到全部的$watch都沒有變化。這樣就可以保證每一個model都已經不會再變化。記住若是循環超過10次的話,它將會拋出一個異常,防止無限循環。當$digest循環結束時,DOM相應地變化。

  例如: 

/*Controller  controllers.js */

app.controller('MainCtrl', function() {

  $scope.name = "Foo";

  $scope.changeFoo = function() {

      $scope.name = "Bar";

  }

});

/*View  index.html */

{{ name }}

<button ng-click="changeFoo()">Change the name</button>

  這裏咱們有一個$watch由於ng-click不生成$watch(函數是不會變的)。

  咱們能夠看出ng的處理流程:

  •咱們按下按鈕;

  •瀏覽器接收到一個事件,進入angular context;

  •$digest循環開始執行,查詢每一個$watch是否變化;

  •因爲監視$scope.name的$watch報告了變化,它會強制再執行一次$digest循環;

  •新的$digest循環沒有檢測到變化;

  •瀏覽器拿回控制權,更新與$scope.name新值相應部分的DOM。

  這裏很重要的是每個進入angular context的事件都會執行一個$digest循環,也就是說每次咱們輸入一個字母循環都會檢查整個頁面的全部$watch。

 

6.1.4如何進入angular context

  誰決定什麼事件進入angular context,而哪些又不進入呢?經過$apply!

  若是當事件觸發時,你調用$apply,它會進入angular context,若是沒有調用就不會進入。如今你可能會問:剛纔的例子裏我也沒有調用$apply啊,爲何?Angular已經作了!所以你點擊帶有ng-click的元素時,時間就會被封裝到一個$apply調用。若是你有一個ng-model="foo"的輸入框,而後你敲一個f,事件就會這樣調用$apply("foo = 'f';")。

  Angular何時不會自動爲咱們$apply呢?

  這是Angular新手共同的痛處。爲何個人jQuery不會更新我綁定的東西呢?由於jQuery沒有調用$apply,事件沒有進入angular context,$digest循環永遠沒有執行。

  咱們來看一個有趣的例子:假設咱們有下面這個directive和controller。

/*Controller  app.js */

app.directive('clickable', function() {

    return {

        restrict: "E",

        scope: {

            foo: '=',

            bar: '='

        },

        template: '<ul style="<li>{{foo}}</li><li>{{bar}}</li></ul>',

        link: function(scope, element, attrs) {

            element.bind('click', function() {

                scope.foo++;

                scope.bar++;

            });

        }

    }

});

app.controller('MainCtrl', function($scope) {

    $scope.foo = 0;

    $scope.bar = 0;

});

  它將foo和bar從controller裏綁定到一個list裏面,每次點擊這個元素的時候,foo和bar都會自增1。那咱們點擊元素的時候會發生什麼呢?咱們能看到更新嗎?答案是否認的。由於點擊事件是一個沒有封裝到$apply裏面的常見的事件,這意味着咱們會失去咱們的計數嗎?不會。

  真正的結果是:$scope確實改變了,可是沒有強制$digest循環,監視foo 和bar的$watch沒有執行。也就是說若是咱們本身執行一次$apply那麼這些$watch就會看見這些變化,而後根據須要更新DOM。

  執行$apply:

element.bind('click', function() {

    scope.foo++;

    scope.bar++;

    scope.$apply();

});

  $apply是咱們的$scope(或者是direcvie裏的link函數中的scope)的一個函數,調用它會強制一次$digest循環(除非當前正在執行循環,這種狀況下會拋出一個異常,這是咱們不須要在那裏執行$apply的標誌)。

  更好的使用$apply的方法:

element.bind('click', function() {

    scope.$apply(function() {

        scope.foo++;

        scope.bar++;

    });

})

 

  有什麼不同的?差異就是在第一個版本中,咱們是在angular context的外面更新的數據,若是有發生錯誤,Angular永遠不知道。很明顯在這個像個小玩具的例子裏面不會出什麼大錯,可是想象一下咱們若是有個alert框顯示錯誤給用戶,而後咱們有個第三方的庫進行一個網絡調用而後失敗了,若是咱們不把它封裝進$apply裏面,Angular永遠不會知道失敗了,alert框就永遠不會彈出來了。

  所以,若是你想使用一個jQuery插件,而且要執行$digest循環來更新你的DOM的話,要確保你調用了$apply。

  有時候我想多說一句的是有些人在不得不調用$apply時會「感受不妙」,由於他們會以爲他們作錯了什麼。其實不是這樣的,Angular不是什麼魔術師,他也不知道第三方庫想要更新綁定的數據。

 

6.1.5使用$watch來監視

  你已經知道了咱們設置的任何綁定都有一個它本身的$watch,當須要時更新DOM,可是咱們若是要自定義本身的watches呢?簡單,來看個例子:

/*Controller  app.js */

app.controller('MainCtrl', function($scope) {

    $scope.name = "Angular";

    $scope.updated = -1;

    $scope.$watch('name', function() {

        $scope.updated++;

    });

});

/*View  index.html*/

<body ng-controller="MainCtrl">

    < input ng-model="name" /> 

  Name updated: { { updated } } times. </body>

  這就是咱們創造一個新的$watch的方法。第一個參數是一個字符串或者函數,在這裏是只是一個字符串,就是咱們要監視的變量的名字,在這裏,$scope.name(注意咱們只須要用name)。第二個參數是當$watch說我監視的表達式發生變化後要執行的。咱們要知道的第一件事就是當controller執行到這個$watch時,它會當即執行一次,所以咱們設置updated爲-1。

  例子2:

/*Controller  app.js */

app.controller('MainCtrl', function($scope) {

    $scope.name = "Angular";

    $scope.updated = 0;
 
    $scope.$watch('name', function(newValue, oldValue) {

        if (newValue === oldValue) { return; } // AKA first run

        $scope.updated++;

    });

});

/*View  index.html*/

<body ng-controller="MainCtrl">

  <input ng-model="name" />

  Name updated: {{updated}} times.

</body>

  watch的第二個參數接受兩個參數,新值和舊值。咱們能夠用他們來略過第一次的執行。一般你不須要略過第一次執行,但在這個例子裏面你是須要的。

  例子3:

/*Controller  app.js */

app.controller('MainCtrl', function($scope) {

    $scope.user = { name: "Fox" };

    $scope.updated = 0;

    $scope.$watch('user', function(newValue, oldValue) {

        if (newValue === oldValue) { return; }

        $scope.updated++;

    });

});

/*View  index.html*/

<body ng-controller="MainCtrl">

  <input ng-model="user.name" />

  Name updated: {{updated}} times.

</body>

  咱們想要監視$scope.user對象裏的任何變化,和之前同樣這裏只是用一個對象來代替前面的字符串。

  呃?沒用,爲啥?由於$watch默認是比較兩個對象所引用的是否相同,在例子1和2裏面,每次更改$scope.name都會建立一個新的基本變量,所以$watch會執行,由於對這個變量的引用已經改變了。在上面的例子裏,咱們在監視$scope.user,當咱們改變$scope.user.name時,對$scope.user的引用是不會改變的,咱們只是每次建立了一個新的$scope.user.name,可是$scope.user永遠是同樣的。

  例子4:

/*Controller  app.js */

app.controller('MainCtrl', function($scope) {

    $scope.user = {
        name: "Fox"
    };

    $scope.updated = 0;

    $scope.$watch('user', function(newValue, oldValue) {

        if(newValue === oldValue) {
            return;
        }

        $scope.updated++;

    }, true);

});

/*View  index.html*/
<body ng-controller="MainCtrl"> <input ng-model="user.name" /> Name updated: {{updated}} times. </body>

  如今有用了吧!由於咱們對$watch加入了第三個參數,它是一個bool類型的參數,表示的是咱們比較的是對象的值而不是引用。因爲當咱們更新$scope.user.name時$scope.user也會改變,因此可以正確觸發。

 

6.1.6 總結

  我但願大家已經學會了在Angular中數據綁定是如何工做的。我猜測你的第一印象是dirty-checking很慢,好吧,實際上是不對的。它像閃電般快。可是,若是你在一個模版裏有2000-3000個watch,它會開始變慢。可是我以爲若是你達到這個數量級,就能夠找個用戶體驗專家諮詢一下了。

  不管如何,隨着ECMAScript6的到來,在Angular將來的版本里咱們將會有Object.observe那樣會極大改善$digest循環的速度。

 

6.2自定義指令詳解

  angular的指令機制。angular經過指令的方式實現了HTML的擴展,加強後的HTML不只長相面目一新,同時也得到了不少強大的技能。更厲害的是,你還能夠自定義指令,這就意味着HTML標籤的範圍能夠擴展到無窮大。angular賦予了你造物主的能力。既然是做爲angular的精華之一,相應的指令相關的知識也不少的。

 

6.2.1指令的編譯過程

  在開始自定義指令以前,咱們有必要了解一下指令在框架中的執行流程:

  1.瀏覽器獲得 HTML 字符串內容,解析獲得 DOM 結構。

  2.ng 引入,把 DOM 結構扔給 $compile 函數處理:

    ① 找出 DOM 結構中有變量佔位符;

    ② 匹配找出 DOM 中包含的全部指令引用;

    ③ 把指令關聯到 DOM;

    ④ 關聯到 DOM 的多個指令按權重排列;

    ⑤ 執行指令中的 compile 函數(改變 DOM 結構,返回 link 函數);

    ⑥ 獲得的全部 link 函數組成一個列表做爲 $compile 函數的返回。

  3. 執行 link 函數(鏈接模板的 scope)。

  這裏注意區別一下$compile和compile,前者是ng內部的編譯服務,後者是指令中的編譯函數,二者發揮做用的範圍不一樣。compile和link函數息息相關又有所區別,這個在後面會講。瞭解執行流程對後面的理解會有幫助。

  在這裏有些人可能會問,angular不就是一個js框架嗎,怎麼還能跟編譯扯上呢,又不是像C++那樣的高級語言。其實此編譯非彼編譯,ng編譯的工做是解析指令、綁定監聽器、替換模板中的變量等。由於工做方式很像高級語言編輯中的遞歸、堆棧過程,因此起名爲編譯,不要疑惑。

 

6.2.2指令的使用方式及命名方法

  指令的幾種使用方式以下:

  • 做爲標籤:<my-dir></my-dir>
  • 做爲屬性:<span my-dir="exp"></span>
  • 做爲註釋:<!-- directive: my-dir exp -->
  • 做爲類名:<span class="my-dir: exp;"></span>

  其實經常使用的就是做爲標籤和屬性,下面兩種用法目前還沒見過,感受就是用來賣萌的,姑且留個印象。咱們自定義的指令就是要支持這樣的用法。

  關於自定義指令的命名,你能夠隨便怎麼起名字都行,官方是推薦用[命名空間-指令名稱]這樣的方式,像ng-controller。不過你可千萬不要用ng-前綴了,防止與系統自帶的指令重名。另一個需知道的地方,指令命名時用駝峯規則,使用時用-分割各單詞。如:定義myDirective,使用時像這樣:<my-directive>。

 

6.2.3自定義指令的配置參數

  下面是定義一個標準指令的示例,可配置的參數包括如下部分:

myModule.directive('namespaceDirectiveName', function factory(injectables) {

    var directiveDefinitionObject = {

        restrict: string, //指令的使用方式,包括標籤,屬性,類,註釋

        priority: number, //指令執行的優先級

        template: string, //指令使用的模板,用HTML字符串的形式表示

        templateUrl: string, //從指定的url地址加載模板

        replace: bool, //是否用模板替換當前元素,若爲false,則append在當前元素上

        transclude: bool, //是否將當前元素的內容轉移到模板中

        scope: bool or object, //指定指令的做用域

        controller: function controllerConstructor($scope, $element, $attrs, $transclude) {...
        }, //定義與其餘指令進行交互的接口函數

        require: string, //指定須要依賴的其餘指令

        link: function postLink(scope, iElement, iAttrs) {...
        }, //以編程的方式操做DOM,包

        括添加監聽器等

        compile: function compile(tElement, tAttrs, transclude) {

                return: {

                    pre: function preLink(scope, iElement, iAttrs, controller) {...
                    },

                    post: function postLink(scope, iElement, iAttrs, controller) {...
                    }

                }

            } //編程的方式修改DOM模板的副本,能夠返回連接函數

    };

    return directiveDefinitionObject;

}); 

  看上去好複雜的樣子,定義一個指令須要這麼多步驟嘛?固然不是,你能夠根據本身的須要來選擇使用哪些參數。事實上priority和compile用的比較少,template和templateUrl又是互斥的,二者選其一便可。因此沒必要緊張,接下來分別學習一下這些參數:

  l 指令的表現配置參數:restrict、template、templateUrl、replace、transclude;

  l 指令的行爲配置參數:compile和link;

  l 指令劃分做用域配置參數:scope;

  l 指令間通訊配置參數:controller和require。

 

6.2.3指令的表現參數restrict等

  指令的表現配置參數:restrict、template、templateUrl、replace、transclude。

  我將先從一個簡單的例子開始。例子的代碼以下:

var app = angular.module('MyApp', [], function() {
    console.log('here')
});

app.directive('sayHello', function() {

    return {

        restrict: 'E',

        template: '<div>hello</div>'

    };

})   

  而後在頁面中,咱們就能夠使用這個名爲sayHello的指令了,它的做用就是輸出一個hello單詞。像這樣使用:

 

<say-hello></say-hello>     

  這樣頁面就會顯示出hello了,看一下生成的代碼:

<say-hello>

    <div>hello</div>

</say-hello>

 

   稍稍解釋一下咱們用到的兩個參數,restirct用來指定指令的使用類型,其取值及含義以下:

取值

含義

使用示例

E

標籤

<my-menu title=Products></my-menu>

A

屬性

<div my-menu=Products></div>

C

<div class="my-menu":Products></div>

M

註釋

<!--directive:my-menu Products-->

  默認值是A。也能夠使用這些值的組合,如EA,EC等等。咱們這裏指定爲E,那麼它就能夠像標籤同樣使用了。若是指定爲A,咱們使用起來應該像這樣:

<div say-hello></div>

 

  從生成的代碼中,你也看到了template的做用,它就是描述你的指令長什麼樣子,這部份內容將出如今頁面中,即該指令所在的模板中,既然是模板中,template的內容中也能夠使用ng-modle等其餘指令,就像在模板中使用同樣。

  在上面生成的代碼中,咱們看到了<div>hello</div>外面還包着一層<say-hello>標籤,若是咱們不想要這一層多餘的東西了,replace就派上用場了,在配置中將replace賦值爲true,將獲得以下結構:

  <div>hello</div>

  replace的做用正如其名,將指令標籤替換爲了temple中定義的內容。不寫的話默認爲false。

  上面的template未免也太簡單了,若是你的模板HTML較複雜,如自定義一個ui組件指令,難道要拼接老長的字符串?固然不須要,此時只需用templateUrl即可解決問題。你能夠將指令的模板單獨命名爲一個html文件,而後在指令定義中使用templateUrl指定好文件的路徑便可,如: 

templateUrl : ‘helloTemplate.html’      

  系統會自動發一個http請求來獲取到對應的模板內容。是否是很方便呢,你不用糾結於拼接字符串的煩惱了。若是你是一個追求完美的有考慮性能的工程師,可能會發問:那這樣的話豈不是要犧牲一個http請求?這也不用擔憂,由於ng的模板還能夠用另一種方式定義,那就是使用<script>標籤。使用起來以下:

<script type="text/ng-template" id="helloTemplate.html">

     <div>hello</div>

</script>        

  你能夠把這段代碼寫在頁面頭部,這樣就沒必要去請求它了。在實際項目中,你也能夠將全部的模板內容集中在一個文件中,只加載一次,而後根據id來取用

 

  接下來咱們來看另外一個比較有用的配置:transclude,定義是否將當前元素的內容轉移到模板中。看解釋有點抽象,不過親手試試就很清楚了,看下面的代碼(例06):

app.directive('sayHello', function() {

    return {

        restrict: 'E',

        template: '<div>hello,<b ng-transclude></b>!</div>',

        replace: true,

        transclude: true

    };

})

  指定了transclude爲true,而且template修改了一下,加了一個<b>標籤,並在上面使用了ng-transclude指令,用來告訴指令把內容轉移到的位置。那咱們要轉移的內容是什麼呢?請看使用指令時的變化:

  <say-hello>美女</say-hello>

  內容是什麼你也看到了哈~在運行的時候,美女將會被轉移到<b>標籤中,原來此配置的做用就是——乾坤大挪移!看效果:

  hello, 美女!

  這個仍是頗有用的,由於你定義的指令不可能總是那麼簡單,只有一個空標籤。當你須要對指令中的內容進行處理時,此參數便大有可用。

 

6.2.4指令的行爲參數:compile和link

  6.2.3中簡單介紹了自定義一個指令的幾個簡單參數,restrict、template、templateUrl、replace、transclude,這幾個理解起來相對容易不少,由於它們只涉及到了表現,而沒有涉及行爲。咱們繼續學習ng自定義指令的幾個重量級參數:compile和link

  l 理解compile和link

  不知你們有沒有這樣的感受,本身定義指令的時候跟寫jQuery插件有幾分類似之處,都是先預先定義好頁面結構及監聽函數,而後在某個元素上調用一下,該元素便擁有了特殊的功能。區別在於,jQuery的側重點是DOM操做,而ng的指令中除了能夠進行DOM操做外,更注重的是數據和模板的綁定。jQuery插件在調用的時候纔開始初始化,而ng指令在頁面加載進來的時候就被編譯服務($compile)初始化好了。

  在指令定義對象中,有compile和link兩個參數,它們是作什麼的呢?從字面意義上看,編譯、連接,貌似太抽象了點。其實可大有內涵,爲了在自定義指令的時候能正確使用它們,如今有必要了解一下ng是如何編譯指令的。

  l 指令的解析流程詳解

  咱們知道ng框架會在頁面載入完畢的時候,根據ng-app劃定的做用域來調用$compile服務進行編譯,這個$compile就像一個大總管同樣,清點做用域內的DOM元素,看看哪些元素上使用了指令(如<div ng-modle=」m」></div>),或者哪些元素自己就是個指令(如<mydierc></mydirec>),或者使用了插值指令( {{}}也是一種指令,叫interpolation directive),$compile大總管會把清點好的財產作一個清單,而後根據這些指令的優先級(priority)排列一下,真是個細心的大總管哈~大總管還會根據指令中的配置參數(template,place,transclude等)轉換DOM,讓指令「初具人形」。

  而後就開始按順序執行各指令的compile函數,注意此處的compile可不是大總管$compile,人家帶着$是土豪,此處執行的compile函數是咱們指令中配置的,compile函數中能夠訪問到DOM節點並進行操做,其主要職責就是進行DOM轉換,每一個compile函數執行完後都會返回一個link函數,這些link函數會被大總管匯合一下組合成一個合體後的link函數,爲了好理解,咱們能夠把它想象成葫蘆小金剛,就像是進行了這樣的處理。

  //合體後的link函數

function AB(){

  A(); //子link函數

  B(); //子link函數

}  

  接下來進入link階段,合體後的link函數被執行。所謂的連接,就是把view和scope連接起來。連接成啥樣呢?就是咱們熟悉的數據綁定,經過在DOM上註冊監聽器來動態修改scope中的數據,或者是使用$watchs監聽 scope中的變量來修改DOM,從而創建雙向綁定。由此也能夠判定,葫蘆小金剛能夠訪問到scope和DOM節點。

  不要忘了咱們在定義指令中還配置着一個link參數呢,這麼多link千萬別搞混了。那這個link函數是幹嗎的呢,咱們不是有葫蘆小金剛了嘛?那我告訴你,其實它是一個小三。此話怎講?compile函數執行後返回link函數,但若沒有配置compile函數呢?葫蘆小金剛天然就不存在了。 

  正房不在了,固然就輪到小三出馬了,大總管$compile就把這裏的link函數拿來執行。這就意味着,配置的link函數也能夠訪問到scope以及DOM節點。值得注意的是,compile函數一般是不會被配置的,由於咱們定義一個指令的時候,大部分狀況不會經過編程的方式進行DOM操做,而更多的是進行監聽器的註冊、數據的綁定。因此,小三名正言順的被大總管寵愛。

  聽完了大總管、葫蘆小金剛和小三的故事,你是否是對指令的解析過程比較清晰了呢?不過細細推敲,你可能仍是會以爲情節生硬,有些細節彷佛仍是沒有透徹的明白,因此還須要再理解下面的知識點:

 

  l compile和link的區別

  其實在我看完官方文檔後就一直有疑問,爲何監聽器、數據綁定不能放在compile函數中,而恰恰要放在link函數中?爲何有了compile還須要link?就跟你質疑我編的故事同樣,爲何最後小三被寵愛了?因此咱們有必要探究一下,compile和link之間到底有什麼區別。好,正房與小三的PK如今開始。

  首先是性能。舉個例子:

<ul>

  <li ng-repeat="a in array">

    <input ng-modle=」a.m」 />

  </li>

</ul> 

  咱們的觀察目標是ng-repeat指令。假設一個前提是不存在link。大總管$compile在編譯這段代碼時,會查找到ng-repeat,而後執行它的compile函數,compile函數根據array的長度複製出n個<li>標籤。而複製出的<li>節點中還有<input>節點而且使用了ng-modle指令,因此compile還要掃描它並匹配指令,而後綁定監聽器。每次循環都作如此多的工做。而更加糟糕的一點是,咱們會在程序中向array中添加元素,此時頁面上會實時更新DOM,每次有新元素進來,compile函數都把上面的步驟再走一遍,豈不是要累死了,這樣性能必然不行。

  如今扔掉那個假設,在編譯的時候compile就只管生成DOM的事,碰到須要綁定監聽器的地方先存着,有幾個存幾個,最後把它們彙總成一個link函數,而後一併執行。這樣就輕鬆多了,compile只須要執行一次,性能天然提高。

  另一個區別是能力。

  儘管compile和link所作的事情差很少,但它們的能力範圍仍是不同的。好比正房能管你的存款,小三就不能。小三能給你初戀的感受,正房卻不能。  

  咱們須要看一下compile函數和link函數的定義:  

function compile(tElement, tAttrs, transclude) { ... }

function link(scope, iElement, iAttrs, controller) { ... }      

  這些參數都是經過依賴注入而獲得的,能夠按需聲明使用。從名字也容易看出,兩個函數各自的職責是什麼,compile能夠拿到transclude,容許你本身編程管理乾坤大挪移的行爲。而link中能夠拿到scope和controller,能夠與scope進行數據綁定,與其餘指令進行通訊。二者雖然均可以拿到element,可是仍是有區別的,看到各自的前綴了吧?compile拿到的是編譯前的,是從template裏拿過來的,而link拿到的是編譯後的,已經與做用域創建了

關聯,這也正是link中能夠進行數據綁定的緣由。

  我暫時只能理解到這個程度了。實在不想理解這些知識的話,只要簡單記住一個原則就好了:若是指令只進行DOM的修改,不進行數據綁定,那麼配置在compile函數中,若是指令要進行數據綁定,那麼配置在link函數中。

 

6.2.5指令的劃分做用域參數:scope

  咱們在上面寫了一個簡單的<say-hello></say-hello>,可以跟美女打招呼。可是看看人家ng內置的指令,都是這麼用的:ng-model=」m」,ng-repeat=」a in array」,不僅僅是做爲屬性,還能夠賦值給它,與做用域中的一個變量綁定好,內容就能夠動態變化了。假如咱們的sayHello能夠這樣用:<say-hello speak=」content」>美女</say-hello>,把要對美女說的話寫在一個變量content中,而後只要在controller中修改content的值,頁面就能夠顯示對美女說的不一樣的話。這樣就靈活多了,不至於見了美女只會說一句hello,而後就沒有而後。

  爲了實現這樣的功能,咱們須要使用scope參數,下面來介紹一下。

  使用scope爲指令劃分做用域

  顧名思義,scope確定是跟做用域有關的一個參數,它的做用是描述指令與父做用域的關係,這個父做用域是指什麼呢?想象一下咱們使用指令的場景,頁面結構應該是這個樣子:

<div ng-controller="testC">

    <say-hello speak="content">美女</say-hello>

</div>

  

  外層確定會有一個controller,而在controller的定義中大致是這個樣子:

app.directive('sayHello', function() {

    return {

        restrict: 'E',

        template: '<div>hello,<b ng-transclude></b>!</div>',

        replace: true,

        transclude: true

    };

})

 

  所謂sayHello的父做用域就是這個名叫testC的控制器所管轄的範圍,指令與父做用域的關係能夠有以下取值:

取值

說明

false

默認值。使用父做用域做爲本身的做用域

true

新建一個做用域,該做用域繼承父做用域

javascript對象

與父做用域隔離,並指定能夠從父做用域訪問的變量

  乍一看取值爲false和true好像沒什麼區別,由於取值爲true時會繼承父做用域,即父做用域中的任何變量均可以訪問到,效果跟直接使用父做用域差很少。但細細一想仍是有區別的,有了本身的做用域後就能夠在裏面定義本身的東西,與跟父做用域混在一塊兒是有本質上的區別。比如是父親的錢你想花多少花多少,可你本身掙的錢父親能花多少就很差說了。你若想看這兩個做用域的區別,能夠在link函數中打印出來看看,還記得link函數中能夠訪問到scope吧。

  最有用的仍是取值爲第三種,一個對象,能夠用鍵值來顯式的指明要從父做用域中使用屬性的方式。當scope值爲一個對象時,咱們便創建了一個與父層隔離的做用域,不過也不是徹底隔離,咱們能夠手工搭一座橋樑,並放行某些參數。咱們要實現對美女說各類話就得靠這個。使用起來像這樣:

scope: {

    attributeName1: 'BINDING_STRATEGY',

    attributeName2: 'BINDING_STRATEGY',
    ...

}  

  鍵爲屬性名稱,值爲綁定策略。等等!啥叫綁定策略?最討厭冒新名詞卻不解釋的行爲!別急,聽我慢慢道來。

 

  先說屬性名稱吧,你是否是認爲這個attributeName1就是父做用域中的某個變量名稱?錯!其實這個屬性名稱是指令本身的模板中要使用的一個名稱,並不對應父做用域中的變量,稍後的例子中咱們來講明。再來看綁定策略,它的取值按照以下的規則:

符號

說明

舉例

@

傳遞一個字符串做爲屬性的值

str : ‘@string’

=

使用父做用域中的一個屬性,綁定數據到指令的屬性中

name : ‘=username’

&

使用父做用域中的一個函數,能夠在指令中調用

getName : ‘&getUserName’

  總之就是用符號前綴來講明如何爲指令傳值。你確定火燒眉毛要看例子了,咱們結合例子看一下,小二,上栗子~

  我想要實現上面想像的跟美女多說點話的功能,即咱們給sayHello指令加一個屬性,經過給屬性賦值來動態改變說話的內容 主要代碼以下:

app.controller('testC', function($scope) {

    $scope.content = '今每天氣真好!';

});

app.directive('sayHello', function() {

    return {

        restrict: 'E',

        template: '<div>hello,<b ng-transclude></b>,{{ cont }}</div>',

        replace: true,

        transclude: true,

        scope: {

            cont: '=speak'

        }

    };

});

  而後在模板中,咱們以下使用指令:

<div ng-controller="testC">

    <say-hello speak=" content ">美女</say-hello>

</div>

  看看運行效果:

  美女今每天氣真好!

  執行的流程是這樣的:

    ① 指令被編譯的時候會掃描到template中的{ {cont} },發現是一個表達式;

    ② 查找scope中的規則:經過speak與父做用域綁定,方式是傳遞父做用域中的屬性;

    ③ speak與父做用域中的content屬性綁定,找到它的值「今每天氣真好!」;

    ④ 將content的值顯示在模板中。

  這樣咱們說話的內容content就跟父做用域綁定到了一其,若是動態修改父做用域的content的值,頁面上的內容就會跟着改變,正如你點擊「換句話」所看到的同樣。

  這個例子也過小兒科了吧!簡單雖簡單,但可讓咱們理解清楚,爲了檢驗你是否是真的明白了,能夠思考一下如何修改指令定義,能讓sayHello以以下兩種方式使用:

<span say-hello speak="content">美女</span>

<span say-hello="content" >美女</span>

  答案我就不說了,簡單的很。下面有更重要的事情要作,咱們說好了要寫一個真正能用的東西來着。接下來就結合所學到的東西來寫一個摺疊菜單,即點擊可展開,再點擊一次就收縮回去的菜單。

  控制器及指令的代碼以下(例07):

app.controller('testC', function($scope) {

    $scope.title = '我的簡介';

    $scope.text = '你們好,我是一名前端工程師,我正在研究AngularJs,歡迎你們與我交流';

});

app.directive('expander', function() {

    return {

        restrict: 'E',

        templateUrl: 'expanderTemp.html',

        replace: true,

        transclude: true,

        scope: {

            mytitle: '=etitle'

        },

        link: function(scope, element, attris) {

            scope.showText = false;

            scope.toggleText = function() {

                scope.showText = !scope.showText;

            }

        }

    };

});

 

  HTML中的代碼以下:

<script type="text/ng-template" id="expanderTemp.html">

    <div class="mybox">

        <div class="mytitle" ng-click="toggleText()">

            {{mytitle}}

        </div>

        <div ng-transclude ng-show="showText">

        </div>

    </div>

</script>

<div ng-controller="testC">

    <expander etitle="title">{{text}}</expander>

</div>

 

  仍是比較容易看懂的,我只作一點必要的解釋。首先咱們定義模板的時候使用了ng的一種定義方式<script type=」text/ng-template」id="expanderTemp.html">,在指令中就能夠用templateUrl根據這個id來找到模板。指令中的{{mytitle}}表達式由scope參數指定從etitle傳遞,etitle指向了父做用域中的title。爲了實現點擊標題可以展開收縮內容,咱們把這部分邏輯放在了link函數中,link函數能夠訪問到指令的做用域,咱們定義showText屬性來表示內容部分的顯隱,定義toggleText函數來進行控制,而後在模板中綁定好。 若是把showText和toggleText定義在controller中,做爲$scope的屬性呢?顯然是不行的,這就是隔離做用域的意義所在,父做用域中的東西除了title以外統統被屏蔽。

  上面的例子中,scope參數使用了=號來指定獲取屬性的類型爲父做用域的屬性,若是咱們想在指令中使用父做用域中的函數,使用&符號便可,是一樣的原理。

 

6.2.6指令間通訊參數:controller和require

  使用指令來定義一個ui組件是個不錯的想法,首先使用起來方便,只須要一個標籤或者屬性就能夠了,其次是可複用性高,經過controller能夠動態控制ui組件的內容,並且擁有雙向綁定的能力。當咱們想作的組件稍微複雜一點,就不是一個指令能夠搞定的了,就須要指令與指令的協做才能夠完成,這就須要進行指令間通訊。

  想一下咱們進行模塊化開發的時候的原理,一個模塊暴露(exports)對外的接口,另一個模塊引用(require)它,即可以使用它所提供的服務了。ng的指令間協做也是這個原理,這也正是自定義指令時controller參數和require參數的做用。

  controller參數用於定義指令對外提供的接口,它的寫法以下:

controller: function controllerConstructor($scope, $element, $attrs, $transclude)  

  它是一個構造器函數,未來能夠構造出一個實例傳給引用它的指令。爲何叫controller(控制器)呢?其實就是告訴引用它的指令,你能夠控制我。至於能夠控制那些東西呢,就須要在函數體中進行定義了。先看controller能夠使用的參數,做用域、節點、節點的屬性、節點內容的遷移,這些均可以經過依賴注入被傳進來,因此你能夠根據須要只寫要用的參數。關於如何對外暴露接口,咱們在下面的例子來講明。

  require參數即是用來指明須要依賴的其餘指令,它的值是一個字符串,就是所依賴的指令的名字,這樣框架就能按照你指定的名字來從對應的指令上面尋找定義好的controller了。不過還稍稍有點特別的地方,爲了讓框架尋找的時候更輕鬆些,咱們能夠在名字前面加個小小的前綴:^,表示從父節點上尋找,使用起來像這樣:require : ‘^directiveName’,若是不加,$compile服務只會從節點自己尋找。另外還能夠使用前綴:?,此前綴將告訴$compile服務,若是所需的controller沒找到,不要拋出異常。

  所須要瞭解的知識點就這些,接下來是例子時間,依舊是從書上抄來的一個例子,咱們要作的是一個手風琴菜單,就是多個摺疊菜單並列在一塊兒,此例子用來展現指令間的通訊再合適不過。

  首先咱們須要定義外層的一個結構,起名爲accordion,代碼以下:

app.directive('accordion', function() {

    return {

        restrict: 'E',

        template: '<div ng-transclude></div>',

        replace: true,

        transclude: true,

        controller: function() {

            var expanders = [];

            this.gotOpended = function(selectedExpander) {

                angular.forEach(expanders, function(e) {

                    if(selectedExpander != e) {

                        e.showText = false;

                    }

                });

            }

            this.addExpander = function(e) {

                expanders.push(e);

            }

        }

    }

});

  須要解釋的只有controller中的代碼,咱們定義了一個摺疊菜單數組expanders,而且經過this關鍵字來對外暴露接口,提供兩個方法。gotOpended接受一個selectExpander參數用來修改數組中對應expander的showText屬性值,從而實現對各個子菜單的顯隱控制。addExpander方法對外提供向expanders數組增長元素的接口,這樣在子菜單的指令中,即可以調用它把自身加入到accordion中。

  看一下咱們的expander須要作怎樣的修改呢:

app.directive('expander', function() {

    return {

        restrict: 'E',

        templateUrl: 'expanderTemp.html',

        replace: true,

        transclude: true,

        require: '^?accordion',

        scope: {

            title: '=etitle'

        },

        link: function(scope, element, attris, accordionController) {

            scope.showText = false;

            accordionController.addExpander(scope);

            scope.toggleText = function() {

                scope.showText = !scope.showText;

                accordionController.gotOpended(scope);

            }

        }

    };

});

  首先使用require參數引入所需的accordion指令,添加?^前綴表示從父節點查找而且失敗後不拋出異常。而後即可以在link函數中使用已經注入好的accordionController了,調用addExpander方法將本身的做用域做爲參數傳入,以供accordionController訪問其屬性。然後在toggleText方法中,除了要把本身的showText修改之外,還要調用accordionController的gotOpended方法通知父層指令把其餘菜單給收縮起來。

  指令定義好後,咱們就能夠使用了,使用起來以下:

<accordion>

    <expander ng-repeat="expander in expanders" etitle="expander.title">

        {{expander.text}}

    </expander>

</accordion>  

  外層使用了accordion指令,內層使用expander指令,而且在expander上用ng-repeat循環輸出子菜單。請注意這裏遍歷的數組expanders可不是accordion中定義的那個expanders,若是你這麼認爲了,說明仍是對做用域不夠了解。此expanders是ng-repeat的值,它是在外層controller中的,因此,在testC中,咱們須要添加以下數據:

$scope.expanders = [

    {
        title: '我的簡介',

        text: '你們好,我是一名前端工程師,我正在研究AngularJs,歡迎你們與我交流'
    },

    {
        title: '個人愛好',

        text: 'LOL '
    },

    {
        title: '性格',

        text: ' 個人性格就是無性格'
    }

];

 

6.3 性能及調優

6.3.1性能測試

  AnglarJS做爲一款優秀的Web框架,可大大簡化前端開發的負擔。

  AnglarJS很棒,但當處理包含複雜數據結構的大型列表時,其運行速度就會很是慢。

  這是咱們將核心管理頁面遷移到AngularJS過程當中遇到的問題。這些頁面在顯示500行數據時本應該工做順暢,但首個方法的渲染時間竟花費了7秒,太可怕了。後來,咱們發現了在實現過程當中存在兩個主要性能問題。一個與「ng-repeat 」指令有關,另外一個與過濾器有關。

  AngularJS 中的ng-repeat在處理大型列表時,速度爲何會變慢? 

  AngularJS中的ng-repeat在處理2500個以上的雙向數據綁定時速度會變慢。這是因爲AngularJS經過「dirty checking」函數來檢測變化。每次檢測都會花費時間,因此包含複雜數據結構的大型列表將下降你應用的運行速度。

 

  提升性能的先決條件 

  時間記錄指令 

  爲了測量一個列表渲染所花費的時間,咱們寫了一個簡單的程序,經過使用「ng-repeat」的屬性「$last」來記錄時間。時間存放在TimeTracker服務中,這樣時間記錄就與服務器端的數據加載分開了。

// Post repeat directive for logging the rendering time   

angular.module('siApp.services').directive('postRepeatDirective',

    ['$timeout', '$log', 'TimeTracker',

        function($timeout, $log, TimeTracker) {

            return function(scope, element, attrs) {

                if(scope.$last) {

                    $timeout(function() {

                        var timeFinishedLoadingList = TimeTracker.reviewListLoaded();

                        var ref = new Date(timeFinishedLoadingList);

                        var end = new Date();

                        $log.debug("## DOM rendering list took: " + (end - ref) + " ms");

                    });

                }

            };

        }

    ]);

 

// Use in HTML:   

<tr ng-repeat="item in items" post-repeat-directive></tr>  

  Chrome開發者工具的時間軸(Timeline)屬性 

  在Chrome開發者工具的時間軸標籤中,你能夠看見事件、每秒內瀏覽器幀數和內存分配。「memory」工具用來檢測內存泄漏,及頁面所需的內存。當幀速率每秒低於30幀時就會出現頁面閃爍問題。「frames」工具可幫助瞭解渲染性能,還可顯示出一個JavaScript任務所花費的CPU時間。

  經過限制列表的大小進行基本的調優 

  緩解該問題,最好的辦法是限制所顯示列表的大小。可經過分頁、添加無限滾動條來實現。

  分頁,咱們能夠使用AngularJS的「limitTo」過濾器(AngularJS1.1.4版本之後)和「startFrom」過濾器。能夠經過限制顯示列表的大小來減小渲染時間。這是減小渲染時間最高效的方法。

 

6.3.2七大調優法則 

1.渲染沒有數據綁定的列表 

  這是最明顯的解決方案,由於數據綁定是性能問題最可能的根源。若是你只想顯示一次列表,並不須要更新、改變數據,放棄數據綁定是絕佳的辦法。不過惋惜的是,你會失去對數據的控制權,但除了該法,咱們別無選擇。

2.不要使用內聯方法計算數據 

  爲了在控制器中直接過濾列表,不要使用可得到過濾連接的方法。「ng-repeat」會評估每一個表達式。在咱們的案例中,「filteredItems()」返回過濾連接。若是評估過程很慢,它將迅速下降整個應用的速度。

<li ng-repeat="item in filteredItems()"> //這並非一個好方法,由於要頻繁地評估。   

<li ng-repeat="item in items"> //這是要採用的方法

3.使用兩個列表(一個用來進行視圖顯示,一個做爲數據源) 

  將要顯示的列表與總的數據列表分開,是很是有用的模型。你能夠對一些過濾進行預處理,並將存於緩存中的連接應用到視圖上。下面案例展現了基本實現過程。filteredLists變量保存着緩存中的連接,applyFilter方法來處理映射。

/* Controller */

// Basic list    
var items = [
{
    name: "John",
    active: true
}, {
    name: "Adam"
}, {
    name: "Chris"
}, {
    name: "Heather"
}];

// Init displayedList   
$scope.displayedItems = items;

// Filter Cache   
var filteredLists['active'] = $filter('filter')(items, {
    "active": true
});

// Apply the filter   
$scope.applyFilter = function(type) {

    if(filteredLists.hasOwnProperty(type) { // Check if filter is cached   

            $scope.displayedItems = filteredLists[type];

        } else {

            /* Non cached filtering */

        }

    }

    // Reset filter   
    $scope.resetFilter = function() {

        $scope.displayedItems = items;

    }
}

/* View */  

<button ng-click="applyFilter('active')">Select active</button>  

<ul><li ng-repeat="item in displayedItems">{{item.name}}<li></ul>  

 

 

 4.在其餘模板中使用ng-if來代替ng-show 

  若是你用指令、模板來渲染額外的信息,例如經過點擊來顯示列表項的詳細信息,必定要使用  ng-if(AngularJSv. 1.1.5之後)。ng-if可阻止渲染(與ng-show相比)。因此其它DOM和數據綁定可根據須要進行評估。

<li ng-repeat="item in items">

    <p> {{ item.title }} </p>

    <button ng-click="item.showDetails = !item.showDetails">Show details</buttons>  

    <div ng-if="item.showDetails">  

       {{item.details}}  

    </div>  
</li>

 

5.不要使用ng-mouseenter、ng-mouseleave等指令 

  使用內部指令,像ng-mouseenter,AngularJS會使你的頁面閃爍。瀏覽器的幀速率一般低於每秒30幀。使用jQuery建立動畫、鼠標懸浮效果能夠解決該問題。確保將鼠標事件放入jQuery的.live()函數中。

6.關於過濾的小提示:經過ng-show隱藏多餘的元素 

  對於長列表,使用過濾一樣會減低工做效率,由於每一個過濾都會建立一個原始列表的子連接。在不少狀況下,數據沒有變化,過濾結果也會保持不變。因此對數據列表進行預過濾,並根據狀況將它應用到視圖中,會大大節約處理時間。

  在ng-repeat指令中使用過濾器,每一個過濾器會返回一個原始連接的子集。AngularJS 從DOM中移除多餘元素(經過調用 $destroy),同時也會從$scope中移除他們。當過濾器的輸入發生改變時,子集也會隨着變化,元素必須進行從新連接,或着再調用$destroy。

  大部分狀況下,這樣作很好,但一旦用戶常常過濾,或者列表很是巨大,不斷的連接與銷燬將影響性能。爲了加快過濾的速度,你能夠使用ng-show和ng-hide指令。在控制器中,進行過濾,併爲每項添加一個屬性。依靠該屬性來觸發ng-show。結果是,只爲這些元素增長ng-hide類,來代替將它們移除子列表、$scope和DOM。

  觸發ng-show的方法之一是使用表達式語法。ng-show的值由表達式語法來肯定。能夠看下面的例子:

<input ng-model="query"></input>

<li ng-repeat="item in items" ng-show="([item.name] | filter:query).length"> {{item.name}} </li>

<span style="font-size: 14px; line-height: 24px; font-family:; white-space: normal;"></span>

7.關於過濾的小提示:防抖動輸入

  解決第6點提出的持續過濾問題的另外一個方法是防抖動用戶輸入。例如,若是用戶輸入一個搜索關鍵詞,只當用戶中止輸入後,過濾器纔會被激活。使用該防抖動服務的一個很好的解決方案請見: http://jsfiddle.net/Warspawn/6K7Kd/。將它應用到你的視圖及控制器中,以下所示:

/* Controller */

// Watch the queryInput and debounce the filtering by 350 ms.   

$scope.$watch('queryInput', function(newValue, oldValue) {

    if(newValue === oldValue) {
        return;
    }

    $debounce(applyQuery, 350);

});

var applyQuery = function() {

    $scope.filter.query = $scope.query;

};

/* View */

<input ng-model="queryInput" />

<li ng-repeat=i tem in items | filter:filter.query>{{ item.title }} </li>

 

 

7 總結

  angular上手比較難,初學者(特別是習慣了使用JQuery的人)可能不太適應其語法以及思想。隨着對ng探索的一步步深刻,也確實感受到了這一點,尤爲是框架內部的某些執行機制。

7.1頁面效果

  ng-show ng-hide 無動畫效果問題

7.2委派事件(代理事件)

7.2.1 NG循環及事件綁定

<ul>

  <li ng-repeat="a in array">

    <input ng-modle=」a.m」 />

  </li>

</ul>

  Ng會根據array的長度複製出n個<li>標籤。而複製出的<li>節點中還有<input>節點而且使用了ng-modle指令,因此ng會對全部的<input>綁定監聽器(事件)。若是array很大,就會綁定太多的事件,性能出現問題。

7.2.2 jQuery委派事件

  從jQuery1.7開始,提供了.on()附加事件處理程序。

.on( events [, selector ] [, data ], handler(eventObject) ) 

  參數Selector爲一個選擇器字符串,用於過濾出被選中的元素中能觸發事件的後代元素。若是選擇器是 null 或者忽略了該選擇器,那麼被選中的元素老是能觸發事件。

  若是省略selector或者是null,那麼事件處理程序被稱爲直接事件 或者 直接綁定事件 。每次選中的元素觸發事件時,就會執行處理程序,無論它直接綁定在元素上,仍是從後代(內部)元素冒泡到該元素的。

  當提供selector參數時,事件處理程序是指爲委派事件(代理事件)。事件不會在直接綁定的元素上觸發,但當selector參數選擇器匹配到後代(內部元素)的時候,事件處理函數纔會被觸發。jQuery 會從 event target 開始向上層元素(例如,由最內層元素到最外層元素)開始冒泡,而且在傳播路徑上全部綁定了相同事件的元素若知足匹配的選擇器,那麼這些元素上的事件也會被觸發。

  委託事件有兩個優點:他們能在後代元素添加到文檔後,能夠處理這些事件;代理事件的另外一個好處就是,當須要監視不少元素的時候,代理事件的開銷更小。

  例如,在一個表格的 tbody 中含有 1,000 行,下面這個例子會爲這 1,000 元素綁定事

$("#dataTable tbody tr").on("click", function(event) {
    alert($(this).text());
});

  委派事件的方法只有一個元素的事件處理程序,tbody,而且事件只會向上冒泡一層(從被點擊的tr 到 tbody ):

 

$("#dataTable tbody").on("click", "tr", function(event) {
    alert($(this).text());
});

  許多委派的事件處理程序綁定到 document 樹的頂層附近,能夠下降性能。每次發生事件時,jQuery 須要比較從 event target(目標元素) 開始到文檔頂部的路徑中每個元素上全部該類型的事件。爲了得到更好的性能,在綁定代理事件時,綁定的元素最好儘量的靠近目標元素。避免在大型文檔中,過多的在 document 或 document.body 上添加代理事件。

相關文章
相關標籤/搜索