Angularjs (二)

 

1、AngularJS中,子作用域一般都會通過JavaScript原型繼承機制繼承其父作用域的屬性和方法。但有一個例外:在directive中使用scope: { ... },這種方式創建的作用域是一個獨立的"Isolate"作用域,它也有父作用域,但父作用域不在其原型鏈上,不會對父作用域進行原型繼承。這種方式定義作用域通常用於構造可複用的directive組件。

作用域的原型繼承是非常簡單普遍的,甚至你不必關心它的運作。直到你在子作用域中向父作用域的原始類型屬性使用雙向數據綁定2-way data binding,比如Form表單的ng-model爲父作用域中的屬性,且爲原始類型,輸入數據後,它不會如你期望的那樣運行——AngularJS不會把輸入數據寫到你期望的父作用域屬性中去,而是直接在子作用域創建同名屬性並寫入數據。這個行爲符合JavaScript原型繼承機制的行爲。AngularJS新手通常沒有認識到ng-repeat、 ng-switchng-viewng-include 都會創建子作用域, 所以經常出問題。 (見 示例)

避免這個問題的最佳實踐是在ng-model中總使用.,(至於爲什麼只要仔細體會JavaScript原型繼承機制,我相信你就可以明白!!!)。。。參見文章 always have a '.' in your ng-models

比如:

<input type="text" ng-model="someObj.prop1">

優於:

<input type="text" ng-model="prop1">

如果你一定要直接使用原始類型,要注意兩點:

  1. 在子作用域中使用 $parent.parentScopeProperty這樣可以直接修改父作用域的屬性
  2. 在父作用域中定義函數,子作用域通過原型繼承調用函數把值傳遞給父作用域(這種方式極少使用)。

2、AngularJS存在四種作用域:(這篇文章講得很好)

  1. 普通的帶原型繼承的作用域 -- ng-includeng-switchng-controller, directive with scope: true
  2. 普通的帶原型繼承的,並且有賦值行爲的作用域 -- ng-repeatng-repeat爲每一個迭代項創建一個普通的有原型繼承的子作用域,但同時在子作用域中創建新屬性存儲迭代項;
  3. “Isolate”作用域 -- directive with scope: {...}, 該作用域沒有原型繼承,但可以通過'=', '@', 和 '&'與父作用域通信。
  4. “transcluded”作用域 -- directive with transclude: true,它也是普通的帶原型繼承的作用域,但它與“Isolate”作用域是相鄰的好基友。

3、一招制敵 - 玩轉 AngularJS 指令的 Scope (作用域)

 

每當一個指令被創建的時候,都會有這樣一個選擇,是繼承自己的父作用域(一般是外部的Controller提供的作用域或者根作用域($rootScope)),還是創建一個新的自己的作用域,當然AngularJS爲我們指令的scope參數提供了三種選擇,分別是:false,true,{};默認情況下是false

 

(1)、當我們將scope設置爲true的時候,我們就新創建了一個作用域,只不過這個作用域是繼承了我們的父作用域;我覺得可以這樣理解,我們新創建的作用域是一個新的作用域,只不過在初始化的時候,用了父作用域的屬性和方法去填充我們這個新的作用域。它和父作用域不是同一個作用域。

 

(2)、當我們將scope設置爲false的時候,我們創建的指令和父作用域(其實是同一個作用域)共享同一個model模型,所以在指令中修改模型數據,它會反映到父作用域的模型中。

(3)、當我們將scope設置爲{}的時候,意味着我們創建的一個新的與父作用域隔離的新的作用域,這使我們在不知道外部環境的情況下,就可以正常工作,不依賴外部環境。

 

這是一篇對指令的Scope講得很透徹的文章,也有相應的Demo參考! 強烈推薦對Directive的Scope有疑惑的同學去閱讀學習下!

 

4、理解Angular中的$apply()以及$digest()

什麼時候手動調用$apply()方法?

 

       如果AngularJS總是將我們的代碼wrap到一個function中並傳入$apply(),以此來開始一輪$digest循環,那麼什麼時候才需要我們手動地調用$apply()方法呢?實際上,AngularJS對此有着非常明確的要求,就是它只負責對發生於AngularJS上下文環境中的變更會做出自動地響應(即,在$apply()方法中發生的對於models的更改)AngularJSbuilt-in指令就是這樣做的,所以任何的model變更都會被反映到view中。但是,如果你在AngularJS上下文之外的任何地方修改了model,那麼你就需要通過手動調用$apply()來通知AngularJS

你總是應該使用接受一個function作爲參數的$apply()方法。這是因爲當你傳入一個function$apply()中的時候,這個function會被包裝到一個trycatch塊中,所以一旦有異常發生,該異常會被$exceptionHandler service處理。

$digest循環會運行多少次?

       答案是$digest循環不會只運行一次。在當前的一次循環結束後,它會再執行一次循環用來檢查是否有models發生了變化。這就是髒檢查(Dirty Checking),它用來處理在listener函數被執行時可能引起的model變化。因此,$digest循環會持續運行直到model不再發生變化,或者$digest循環的次數達到了10次。因此,儘可能地不要在listener函數中修改model。

Note: $digest循環最少也會運行兩次,即使在listener函數中並沒有改變任何model。正如上面討論的那樣,它會多運行一次來確保models沒有變化。

5、AngularJS 之 Factory vs Service vs Provider

 

  用 Factory 就是創建一個對象,爲它添加屬性,然後把這個對象返回出來。你把 service 傳進 controller 之後,在 controller 裏這個對象裏的屬性就可以通過 factory 使用了。

FactoryExample1

2) Service 是用"new"關鍵字實例化的。因此,你應該給"this"添加屬性,然後 service 返回"this"。你把 service 傳進 controller 之後,在controller裏 "this" 上的屬性就可以通過 service 來使用了。

ServiceExample2

3) Providers 是唯一一種你可以傳進 .config() 函數的 service。當你想要在 service 對象啓用之前,先進行模塊範圍的配置,那就應該用 provider。

ProviderExample3

 

 

什麼時候適合使用factory()方法

在service裏面當我們僅僅需要的是一個方法和數據的集合且不需要處理複雜的邏輯的時候,factory()是一個非常不錯的選擇。
注意:需要使用.config()來配置service的時候不能使用factory()方法

什麼時候適合使用service()方法

service()方法很適合使用在功能控制比較多的service裏面
注意:需要使用.config()來配置service的時候不能使用service()方法

 

什麼時候適合使用provider()方法

當我們希望在應用開始前對service進行配置的時候就需要使用到provider()。比如,我們需要配置services在不同的部署環境裏面(開發,演示,生產)使用不同的後端處理的時候就可以使用到了
當我們打算髮布開源provider()也是首選創建service的方法,這樣就可以使用配置的方式來配置services而不是將配置數據硬編碼寫到代碼裏面。

6、作用域也只不過是在彼此間進行了原型繼承。所有可以用在 JavaScript 對象上的原型繼承的規則,都可以同等的用在 作用域 的原型鏈繼承上去。畢竟 Scopes 作用域就是 JavaScript 對象嘛。

在子級作用域中去改變父級作用域上面的屬性有幾種方法。第一種,我們就直接通過 $parent 屬性來引用父級作用域,但我們要看到,這是一個非常不可靠的解決方案。麻煩之處就在於,ng-model 指令所使用的表達式非常嚴重的依賴於整個DOM結構。比如就在<input> 標籤上面的哪裏插入另一個 可創建作用域 的指令,那 $parent 就會指向一個完全不同的作用域了。

就經驗來講,儘量避免使用 $parent 屬性,因爲它強制的把 AngularJS 表達式和你的模板所創建的 DOM 結構捆綁在了一起。這樣一來,HTML結構的一個小小的改動,都可能會讓整個應用崩潰。

另一個解決方案就是,不要直接把屬性綁定到 作用域上,而是綁到一個對象上面,如下所示:

<body ng-app ng-init="thing = {name : 'World'}"> 
    <h1>Hello, {{thing.name}}</h1>
    <div ng-controller="HelloCtrl">
        Say hello to: <input type="text" ng-model="thing.name">
        <h2>Hello, {{thing.name}}!</h2> 
    </div>
</body>

這個方案會更可靠,因爲他並沒有假設 DOM 樹的結構是什麼樣子。

避免直接把數據綁定到 作用域的屬性上。應優先選擇把數據雙向綁定到對象的屬性上(然後再把對象掛到 scope 上)。
就經驗而言,在給 ng-model 指令的表達式中,你應該有一個點(例如, ng-model="thing.name")。

7、給你一個承諾 - 玩轉 AngularJS 的 Promise

Promise是一種異步方式處理值(或者非值)的方法,promise是對象,代表了一個函數最終可能的返回值或者拋出的異常。

要在AngularJS中使用Promise,要使用AngularJS的內置服務$q

我們可以先使用$qdefer()方法創建一個deferred對象,然後通過deferred對象的promise屬性,將這個對象變成一個promise對象;這個deferred對象還提供了三個方法,分別是resolve(),reject(),notify()

Test1

我們先通過一個同步的例子來創建一個promise對象。

HTML代碼:

<div ng-app="MyApp">
    <div ng-controller="MyController">
        <label for="flag">成功
        <input id="flag" type="checkbox" ng-model="flag" /><br/>
        </label>
        <hr/>
        <button ng-click="handle()">點擊我</button>
    </div>
</div>

JS代碼:

angular.module("MyApp", [])
.controller("MyController", ["$scope", "$q", function ($scope, $q) {
            $scope.flag = true;
            $scope.handle = function () {
            var deferred = $q.defer();
            var promise = deferred.promise;

            promise.then(function (result) {
                alert("Success: " + result);
            }, function (error) {
                alert("Fail: " + error);
            });

            if ($scope.flag) {
                deferred.resolve("you are lucky!");
            } else {
                deferred.reject("sorry, it lost!");
            }
        }
}]);

我們來詳細的分析一下上面的代碼,我們在html頁面上添加了一個checkbox,一個button目的是爲了當我們選中checkbox和不選中checkbox時,點擊下面的按鈕會彈出不同的內容。

var deferred = $q.defer()這段代碼創建了一個deferred對象,我們然後利用var promise = deferred.promise創建了一個promise對象。

我們給給promisethen方法傳遞了兩個處理函數,分別處理當promise被執行的時候以及promise被拒絕的時候所要進行的操作。

下面的一個if(){}else{}語句塊,包含執行和拒絕deferred promise,如果$scope.flagtrue,那麼我們就會執行deferred promise,然後我們給promise傳遞一個值,也可能是一個對象,表明promise執行的結果。如果$scope.flagfalse,那麼我們就會拒絕deferred promise,然後我們給promise傳遞一個值,也可能是一個對象,表明promise被拒絕的原因。

現在回過頭來看看,promisethen方法,如果promise被執行,那麼它的參數中的第一個函數的result就代表了"you are lucky!"

我們暫時用的是同步的模式,爲的是能夠說明問題,後面將會使用異步的方法。

到這裏我們可以瞭解一下$qdefer()方法創建的對象具有哪些方法

  • resolve(value):用來執行deferred promisevalue可以爲字符串,對象等。
  • reject(value):用來拒絕deferred promisevalue可以爲字符串,對象等。
  • notify(value):獲取deferred promise的執行狀態,然後使用這個函數來傳遞它。
  • then(successFunc, errorFunc, notifyFunc):無論promise是成功了還是失敗了,當結果可用之後,then都會立刻異步調用successFunc,或者'errorFunc',在promise被執行或者拒絕之前,notifyFunc可能會被調用0到多次,以提供過程狀態的提示。
  • catch(errorFunc)
  • finally(callback)

8、AngularJS表單基礎

首先,HTML原生的form表單是不能嵌套的,而Angular封裝之後的form就可以嵌套。

其次,Angular爲form擴展了自動校驗、防止重複提交等功能。

有人說,如果我想要使用原生的HTML表單,應該怎麼做呢?

Angular提供了一個ng-pristine指令,把這個指令寫在form標籤中,Angular就知道你想使用原生的form標籤了。

 

Angular爲表單內置了4中CSS樣式。

ng-valid 校驗合法狀態
ng-invalid 校驗非法狀態
ng-pristine 如果要使用原生的form,需要設置這個值
ng-dirty     表單處於髒數據狀態

 

Angular在對錶單進行自動校驗的時候會校驗Model上的屬性,如果不設置ng-model,則Angular無法知道myForm.$invalid這個值是否爲真。

 

9、AngularJs ng-route路由詳解

 

首先需要在頁面引入angular和angular-route,注意要在angular-route之前引入angular

<script src="../../bower_components/angular/angular.js"></script>
<script src="../../bower_components/angular-route/angular-route.js"></script>

這主要是因爲angular-route.js需要傳入window.angular這個參數,而這個參數只有在加載完angular纔會出現。

 

路由功能是由 routeProvider服務 和 ng-view 搭配實現,ng-view相當於提供了頁面模板的掛載點,當切換URL進行跳轉時,不同的頁面模板會放在ng-view所在的位置; 然後通過 routeProvider 配置路由的映射。

一般主要通過兩個方法:

when():配置路徑和參數;

otherwise:配置其他的路徑跳轉,可以想成default。

when的第二個參數:

controller:對應路徑的控制器函數,或者名稱

controllerAs:給控制器起個別名

template:對應路徑的頁面模板,會出現在ng-view處,比如"<div>xxxx</div>"

templateUrl:對應模板的路徑,比如"src/xxx.html"

resolve:這個參數着重說下,該屬性會以鍵值對對象的形式,給路由相關的控制器綁定服務或者值。然後把執行的結果值或者對應的服務引用,注入到控制器中。如果resolve中是一個promise對象,那麼會等它執行成功後,才注入到控制器中,此時控制器會等待resolve中的執行結果。

詳細的例子,可以參考下面的樣例。

redirectTo:重定向地址

reloadOnSearch:設置是否在只有地址改變時,才加載對應的模板;search和params改變都不會加載模板

caseInsensitiveMatch:路徑區分大小寫

 

路由有幾個常用的事件:

$routeChangeStart:這個事件會在路由跳轉前觸發

$routeChangeSuccess:這個事件在路由跳轉成功後觸發

$routeChangeError:這個事件在路由跳轉失敗後觸發

10、$q服務

angular中的$q是用來處理異步的(主要當然是http交互啦~).

$q採用的是promise式的異步編程.什麼是promise異步編程呢? 

異步編程最重要的核心就是回調,因爲有回調函數,所以才構成了異步編程,而回調有三個關鍵部分:

一是什麼時候執行回調,二是執行什麼回調,三是回調執行的時候傳入什麼參數.

 

一. $q.defer():

返回一個對象.一般把它賦值給defer變量:

var defer = $q.defer()

※defer的方法:

  (一)defer.resolve(data)

      對promise發起通知,通知執行成功的回調,回調執行的參數爲data

  (二)defer.reject(data)

      對promise發起通知,通知執行失敗的回調,回調執行的參數爲data

  (三)defer.notify(data)

      對promise發起通知,通知執行進度的回調,回調執行的參數爲data

※defer的屬性:

  (一)defer.promise

  ※defer.promise的屬性:

    1.defer.promise.$$v

           promise的$$v對象就是對應的defer發送的data,當defer還沒有發送通知時,$$v爲空.

           有一點很重要,假設,我們令$scope.a = defer.promise,那麼頁面在渲染{{a}}時,使用的是a.$$v來渲染a這個變量的.並且修改a變量,視圖不會發生變化,需要修改a.$$v,視圖纔會被更新,具體請參考:

           http://www.cnblogs.com/liulangmao/p/3907307.html

  ※defer.promise的方法:

    1.defer.promise.then([success],[error],[notify]):

           .then方法接受三個參數,均爲函數,函數在接受到defer發送通知時被執行,函數中的參數均爲defer發送通知時傳入的data.

           [success]: 成功回調,defer.resolve()時調用

    [error]: 失敗回調,defer.reject()時調用

    [notify]: 進度回調,defer.notify()時調用

           .then()方法返回一個promise對象,可以接續調用.then(),注意,無論.then()是調用的success函數,還是error函數,還是notify函數,發送給下一個promise對象的通知一定是成功通知,而參數則是函數的返回值.也就是說,then()方法裏的函數被執行結束後,即爲下一個promise發送了成功通知,並且把返回值作爲參數傳遞給回調.

 

2.defer.promise.catch([callback])          

    相當於.then(null,[callback])的簡寫. 直接傳入失敗回調.返回一個promise對象.發給下一個promise對象的通知依然是成功通知.data值就是回調的返回值.            

           *很早的angualr版本是沒有這個方法的. 

 

3.defer.promise.finally([callback])

           .finally只接受一個回調函數,而且這個回調函數不接受參數.無論defer發送的通知是成功,失敗,進度,這個函數都會被調用.

           .finally也返回一個promise對象,和上面兩個方法不同的是,它爲下一個promise對象發送的通知不一定是成功通知,而是傳給finally的通知類型.也就是說,如果defer給promise發送的是失敗通知,那麼,finally()得到的promise它收到的也會是失敗通知,得到的參數也不是finally的返回值,而是第一個defer發出的通知所帶的data.

            *很早的angualr版本是沒有這個方法的. 

11、AngularJS $apply vs $digest

 

  • $apply會使ng進入$digest cycle, 並從$rootScope開始遍歷(深度優先)檢查數據變更。
  • $digest僅會檢查該scope和它的子scope,當你確定當前操作僅影響它們時,用$digest可以稍微提升性能。
  • 一些不必要的操作,放到$timeout裏面延遲執行。
  • 如果不涉及數據變更,還可以加上第三個參數false,避免調用$apply
  • directive中執行的$evalAsync, 會在angular操作DOM之後,瀏覽器渲染之前執行。
  • controller中執行的$evalAsync, 會在angular操作DOM之前執行,一般不這麼用。
  • 而使用$timeout,會在瀏覽器渲染之後執行。
  • 對時間有要求的,第二個參數可以設置爲0。

    1

    2

    3

    4

    5

    6

    $http.get('http://path/to/url').success(function(data){

      $scope.name = data.name;

      $timeout(function(){

        //do sth later, such as log

      }, 0, false);

    });

     

 

    詳細:https://github.com/atian25/blog/issues/5

 12、注意 angular.module('myModule', []) 與 angular.module('myModule') 的使用

 

angular.module('myModule', []) will create the module myModule and overwrite any existing module namedmyModule.

>>>angular.module('myModule', []) 創建一個新的module,覆蓋已經存在的module

Use angular.module('myModule') to retrieve an existing module.

>>>angular.module('myModule') 指向已經存在的module

13、angularjs 指令詳解 - template, restrict, replace

14、理解$watch ,$apply 和 $digest --- 理解數據綁定過程

15、AngularJS 表達式 與 JavaScript 表達式

類似於 JavaScript 表達式,AngularJS 表達式可以包含字母,操作符,變量。

與 JavaScript 表達式不同,AngularJS 表達式可以寫在 HTML 中。

與 JavaScript 表達式不同,AngularJS 表達式不支持條件判斷,循環及異常。

與 JavaScript 表達式不同,AngularJS 表達式支持過濾器。

16、運行塊

 

和配置塊不同,運行塊在注入器創建之後被執行,它是所有AngularJS應用中第一個被執行的方法。

運行塊是AngularJS中與main方法最接近的概念。運行塊中的代碼塊通常很難進行單元測試,它是和應用本身高度耦合的。

運行塊通常用來註冊全局的事件監聽器。例如,我們會在.run()塊中設置路由事件的監聽器以及過濾未經授權的請求。

假設我們需要在每次路由發生變化時,都執行一個函數來驗證用戶的權限,放置這個功能唯一合理的地方就是run方法:

angular.module('myApp', ['ngRoute'])
.run(function($rootScope, AuthService) {
    $rootScope.$on('$routeChangeStart', function(evt, next, current) {
        // 如果用戶未登錄
        if (!AuthService.userLoggedIn()) {
            if (next.templateUrl === "login.html") {
                // 已經轉向登錄路由因此無需重定向
            } else {
                $location.path('/login');
            }
        }
    });
});

run()函數接受一個參數。

  • initializeFn(函數)AngularJS在注入器創建後會執行這個函數。

17、AngularJS API之copy深拷貝

angular提供了一個可以複製對象的api——copy(source,destination),它會對source對象執行深拷貝。

使用時需要注意下面幾點:

  • 如果只有一個參數(沒有指定拷貝的對象 destination),則返回一個拷貝對象
  • 如果指定了destination,則會深拷貝對象複製給destination
  • 如果source是null或者undefined,那麼會直接返回source
  • 如果source就是desitination,那麼會報錯。



 

發佈了852 篇原創文章 · 獲贊 1848 · 訪問量 137萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章