angularJS 雙向數據綁定、做用域、表達式、

在Rails等傳統Web框架中,控制器將多個模型中的數據和模板組合在一塊兒造成視圖,並將其提供給用戶,這個組合過程會產生一個單向視圖。
AngularJS則採用了徹底不一樣的解決方案。它建立實時模板來代替視圖而不是將數據合併進模板以後更新DOM。任何一個獨立視圖組件中的值都是動態替換的。這個功能能夠說是AngularJS中最重要的功能之一

javascript

ng-app屬性聲明全部被其包含的內容都屬於這個AngularJS應用,只有被具備ng-app屬性的DOM元素包含的元素纔會受AngularJS影響。

AngularJS會記錄數據模型所包含的數據在任何特定時間點的值,java

當AngularJS認爲某個值可能發生變化時,它會運行本身的事件循環來檢查這個值是否變「髒」。若是該值從上次事件循環運行以後發生了變化,則該值被認爲是「髒」值。這也是Angular能夠跟蹤和響應應用變化的方式

這個事件循環會調用$digest()循環,這個過程被稱做髒檢查(dirty checking) 。髒檢查是檢查數據模型變化的有效手段, AngularJS會在事件循環時執行髒檢查(第24章會深刻討論)來保證數據的一致性。


爲了表示內部和內置的庫函數,Angular使用$預約義對象。儘管這相似於全局的jQuery對象$,但它們是徹底無關的。只要遇到$符號,你均可以只把它看做一個Angular對象。express

簡單的數據綁定

使用ng-model指令將內部數據模型對象($scope)中的name屬性綁定到了文本輸入字段上,不管在文本輸入字段中輸入了什麼,都會同步到數據模型中。$scope對象是一個簡單的JavaScript對象,其中的屬性能夠被視圖訪問,也能夠同控制器進行交互。api

<input type="text" ng-model = "name" ><br>
                <div>hello {{name}}</div>

 

DOM 元 素 上 的ng-controller聲明全部被它包含的元素都屬於某個控制器。
一個顯示時間的小例子: 數組

<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="app.js"></script>

<div ng-app="testmodule">
    <div ng-controller="MyController">
        <span>{{clock}}</span>
    </div>
</div>

app.js  中的內容:瀏覽器

angular.module('testmodule',[])
              .controller("MyController",['$scope',function($scope){
                  $scope.clock = new Date();

                  var up = function(){
                      $scope.clock = new Date();
                  }
                  setInterval(function(){
                      $scope.$apply(up);
                  },1000);

              }]);

 

做用域(scope)

做用域是視圖和控制器之間的膠水。視圖中的模板會和做用域進行鏈接,應用會對DOM進行設置以便將屬性變化通知給AngularJS。

做用域是應用狀態的基礎。基於動態綁定,能夠依賴視圖在修改數據時馬上更新$scope,也能夠依賴$scope在其發生變化時馬上從新渲染視圖。安全

AngularJS將$scope設計成和DOM相似的結構,所以$scope能夠進行嵌套,也就是說咱們能夠引用父級$scope中的屬性。

服務器

做用域提供了監視數據模型變化的能力。它容許開發者使用其中的apply機制,將數據模型的變化在整個應用範圍內進行通知。架構

AngularJS啓動並生成視圖時,會將根ng-app元素同$rootScope進行綁定。 $rootScope是全部$scope對象的最上層。app

 $rootScope是AngularJS中最接近全局做用域的對象,能夠看做是 根做用域,在$rootScope上附加太多業務邏並非好主意,這與污染JavaScript的全局做用域是同樣的。

將應用的業務邏輯都放在控制器中,而將相關的數據都放在控制器的做用域中,這是很是完美的架構。

$scope對象就是一個普通的JavaScript對象,咱們能夠在其上隨意修改或添加屬性。

$scope對象在AngularJS中充當數據模型,$scope並不負責處理和操做數據,

 

做用域能作什麼

有如下的基本功能:
 提供觀察者以監視數據模型的變化;
 能夠將數據模型的變化通知給整個應用,甚至是系統外的組件;
 能夠進行嵌套,隔離業務功能和數據;
 給表達式提供運算時所需的執行環境。

 

$scope 的生命週期

 當Angular關心的事件發生在瀏覽器中時,好比用戶在經過ng-model屬性監控的輸入字段中輸入,或者帶有ng-click屬性的按鈕被點擊時, Angular的事件循環都會啓動
每當事件被處理時, $scope就會對定義的表達式求值。此時事件循環會啓動,而且Angular應用會監控應用程序內的全部對象,髒值檢測循環也會運行。

$scope對象的生命週期處理有四個不一樣階段。

.1 建立

建立控制器或指令時, AngularJS會用$injector建立一個新的做用域,並在這個新建的控制器或指令運行時將做用域傳遞進去。

.2 連接

當Angular開始運行時,全部的$scope對象都會附加或者連接到視圖中。全部建立$scope對象的函數也會將自身附加到視圖中。這些做用域將會註冊當Angular應用上下文中發生變化時須要運行的函數。

這些函數被稱爲$watch函數, Angular經過這些函數獲知什麼時候啓動事件循環。

.3 更新

當事件循環運行時,它一般執行在頂層$scope對象上(被稱做$rootScope),每一個子做用域都執行本身的髒值檢測。每一個監控函數都會檢查變化。若是檢測到任意變化, $scope對象就會觸
髮指定的回調函數。

.4 銷燬

當一個$scope在視圖中再也不須要時,這個做用域將會清理和銷燬本身。

儘管永遠不會須要清理做用域(由於Angular會爲你處理),可是知道是誰建立了這個做用域仍是有用的,由於你可使用這個$scope上叫作$destory()的方法來清理這個做用域。

 

 

表達式

{{ }}符號將一個變量綁定到$scope上的寫法本質上就是一個表達式: {{ expression }}。  當用$watch進行監聽時, AngularJS會對錶達式或函數進行運算。

表達式和eval(javascript)很是類似,可是因爲表達式由AngularJS來處理,它們有如下顯著不一樣的特性:
 全部的表達式都在其所屬的做用域內部執行,並有訪問本地$scope的權限;
 若是表達式發生了TypeError和ReferenceError並不會拋出異常;
 不容許使用任何流程控制功能(條件控制,例如if/eles);
 能夠接受過濾器和過濾器鏈。

 

AngularJS經過$parse【解析】這個內部服務來進行表達式的運算,這個服務可以訪問當前所處的做用域。這個過程容許咱們訪問定義在$scope上的原始JavaScript數據和函數。

將$parse服務注入到控制器中,而後調用它就能夠實現手動解析表達式。舉例來講,若是頁面上有一個輸入框綁定到了expr變量上,
這是一個數據雙向綁定的例子,目前還不知道$parse的用法,

<div ng-controller="ParentController">
     <input type="text" ng-model="expr" placeholder="請輸入"><br>
     <div>{{parseValue}}</div>                               
</div>
var app = angular.module("app",[]);
app.controller("ParentController",['$scope','$parse',function($scope,$parse){
                  $scope.$watch('expr',function(newVal,oddVal,scope){
                      if(newVal !== oddVal){
                          // 讓咱們創建parseFun表達式,目前 $prase()怎麼解析還不知道
                    var parseFun = $parse(newVal);
                  // 獲取記過解析後的表達式的值,放在scope裏,output出來
                    scope.parsedExpr = parseFun(scope);
                      }                          
                  });
}]);

 

插值字符串

在AngularJS中,咱們的確有手動運行模板編譯的能力,例如,在字符串模板中作插值操做,須要在你的對象中注入$interpolate服務。

$interpolate服務是一個能夠接受三個參數的函數,其中第一個參數是必需的。
 text(字符串):一個包含字符插值標記的字符串。
 mustHaveExpression(布爾型):若是將這個參數設爲true,當傳入的字符串中不含有表達式時會返回null。
 trustedContext(字符串): AngularJS會對已經進行過字符插值操做的字符串經過$sec.getTrusted()方法進行嚴格的上下文轉義。

 

        <div class="panel panel-primary" ng-app="app">
            <div class="panel-heading">
                <div class="panel-title">雙向數據綁定</div>
            </div>


            <div class="panel-body" ng-controller="ParentController">
                
                <input type="email" ng-model="name" placeholder="請輸入"><br>
                <textarea ng-model="emailbody" ></textarea>
                
                <pre>{{parseValue}}</pre>    
                                    
            </div>

        </div>    
app.js:

var app = angular.module("app",[]);
app.controller("ParentController",function($scope,$interpolate){
                  $scope.$watch('emailbody',function(body){

                      if(body){
                          var tem = $interpolate(body);
                          $scope.parseValue = tem({name : $scope.name});
                      }
                      
                  });
});

效果:

 

AngularJS 的生命週期

總共有兩個主要階段:  編譯階段 和 連接階段

編譯階段

第一個階段是編譯階段。在編譯階段, AngularJS會遍歷整個HTML文檔並根據JavaScript中的指令定義來處理頁面上聲明的指令。


每個指令的模板中均可能含有另一個指令,另一個指令也可能會有本身的模板。當AngularJS調用HTML文檔根部的指令時,會遍歷其中全部的模板,模板中也可能包含帶有模板的指令。

模板樹可能又大又深,儘管元素能夠被多個指令所支持或修飾,這些指令自己的模板中也能夠包含其餘指令,但只有屬於最高優先級指令的模板會被解析並添加到模板樹中。
將包含模板的指令和添加行爲的指令分離開來。

一旦對指令和其中的子模板進行遍歷或編譯,編譯後的模板會返回一個叫作模板函數的函數。咱們有機會在指令的模板函數被返回前,對編譯後的DOM樹進行修改。
以ng-repeat爲例,它會遍歷指定的數組或對象,在數據綁定以前構建出對應的DOM結構。   只會有不多的性能開銷。

一個指令的表現一旦編譯完成,立刻就能夠經過編譯函數對其進行訪問,編譯函數的簽名包含有訪問指令聲明所在的元素(tElemente)及該元素其餘屬性(tAttrs)的方法。這個編譯函數返回前面提到的模板函數,其中含有完整的解析樹。

 

編譯函數:conpile()

compile函數 能夠返回一個對象或函數。理解compile和link選項是AngularJS中須要深刻討論的高級話題之一,對於瞭解AngularJS到底是如何工做的相當重要。
compile選項自己並不會被頻繁使用,可是link函數則會被常用。本質上,當咱們設置了link選項,其實是建立了一個postLink()連接函數,以便compile()函數能夠定義連接函數。
若是設置了compile函數,說明咱們但願在指令和實時數據被放到DOM中以前進行DOM操做在這個函數中進行諸如添加和刪除節點等DOM操做是安全的

compile和link選項是互斥的。若是同時設置了這兩個選項,那麼會把compile所返回的函數看成連接函數,而link選項自己則會被忽略。

compile: function(tEle, tAttrs, transcludeFn) {
    var tplEl = angular.element('<div>' +'<h2></h2>' +'</div>');
    var h2 = tplEl.find('h2');
    h2.attr('type', tAttrs.type);
    h2.attr('ng-model', tAttrs.ngModel);
    h2.val("hello");
    tEle.replaceWith(tplEl);
    
    return function(scope, ele, attrs) {
        // 鏈接函數
    };
}

不要進行DOM事件監聽器的註冊:這個操做應該在連接函數(link)中完成。

編譯函數負責對模板DOM進行轉換。    連接函數負責將做用域和DOM進行連接。 

 

link函數

用link函數建立能夠操做DOM的指令。

連接函數是可選的。若是定義了編譯函數,它會返回連接函數,所以當兩個函數都定義了時,編譯函數會重載連接函數。

若是咱們的指令很簡單,而且不須要額外的設置,能夠從工廠函數 (回調函數)返回一個函數來代替對象。若是這樣作了,這個函數就是連接函數

下面兩種定義指令的方式在功能上是徹底同樣的:

angular.module('myApp', [])
       .directive('myDirective', function() {
            return {
                    pre: function(tElement, tAttrs, transclude) {
                        // 在子元素被連接以前執行
                        // 在這裏進行Don轉換不安全
                        // 以後調用'lihk'h函數將沒法定位要連接的元素
                    },
                    post: function(scope, iElement, iAttrs, controller) {
                        // 在子元素被連接以後執行
                        // 若是在這裏省略掉編譯選項
                        //在這裏執行DOM轉換和連接函數同樣安全嗎
                    }
            };
        });

angular.module('myApp', [])
       .directive('myDirective', function() {
            return {
                link: function(scope, ele, attrs) {
                    return {
                            pre: function(tElement, tAttrs, transclude) {
                                // 在子元素被連接以前執行
                                // 在這裏進行Don轉換不安全
                                // 以後調用'lihk'h函數將沒法定位要連接的元素
                            },
                            post: function(scope, iElement, iAttrs, controller) {
                                // 在子元素被連接以後執行
                                // 若是在這裏省略掉編譯選項
                                //在這裏執行DOM轉換和連接函數同樣安全嗎
                            }
                        
                    }
                }
            }
});

當定義了編譯函數來取代連接函數時,連接函數是咱們能提供給返回的 就是 postLink函數。

連接函數的做用。它會在模板編譯並同做用域進行連接後被調用,所以它負責設置事件監聽器,監視數據變化和實時的操做DOM。

link函數對綁定了實時數據的DOM具備控制能力,所以須要考慮性能問題。在選擇是用編譯函數仍是連接函數實現功能時,將性能影響考慮進去。

連接函數的簽名以下:
link: function(scope, element, attrs) {
  // 在這裏操做DOM
}

若是指令定義中有require選項,函數簽名中會有第四個參數,表明着所依賴的控制器 或者 指令的控制器
  // require 'SomeController',
  link: function(scope, element, attrs, SomeController) {
      // 在這裏操做DOM,能夠訪問required指定的控制器
  }

ngMode

ngModel是一個用法特殊的指令,它提供更底層的API來處理控制器內的數據。

ngModel控制器會隨ngModel被一直注入到指令中,其中包含了一些方法。爲了訪問ngModelController必須使用require設置(像前面的例子中那樣):

angular.module('myApp')
.directive('myDirective',function(){
    return {
        require: '?ngModel',
        link: function(scope, ele, attrs, ngModel) {
            if (!ngModel) return;
        // 如今咱們的指令中已經有ngModelController的一個實例
        }
    };
});

若是不設置require選項, ngModelController就不會被注入到指令中。

這個指令沒有隔離做用域。若是給這個指令設置隔離做用域,將致使內部ngModel沒法更新外部ngModel的對應值: AngularJS會在本地做用域之外查詢值。

設置做用域中的視圖值,調用ngModel.$setViewValue()函數。 ngModel.$setViewValue()函數能夠接受一個參數,參數是咱們想要賦值給ngModel實例的實際值。這個方法會更新控制器上本地的$viewValue,而後將值傳遞給每個$parser函數(包括驗證器)。

當值被解析,且$parser流水線中全部的函數都調用完成後,值會被賦給$modelValue屬性,而且傳遞給指令中ng-model屬性提供的表達式。
全部步驟都完成後, $viewChangeListeners中全部的監聽器都會被調用。注意,單獨調用$setViewValue()不會喚起一個新的digest循環,所以若是想更新指令,須要在設置$viewValue後手動觸發digest。

$setViewValue()方法適合於在自定義指令中監聽自定義事件(好比使用具備回調函數的jQuery插件),咱們會但願在回調時設置$viewValue並執行digest循環。

angular.module('myApp')
.directive('myDirective', function() {
    return {
        require: '?ngModel',
        link: function(scope, ele, attrs, ngModel) {
            if (!ngModel) return;
            $(function() {
                ele.datepicker({
                    onSelect: function(date) {
                        // 設置視圖和調用apply
                        scope.$apply(function() {
                            ngModel.$setViewValue(date);
                        });
                    }
                });
            });
        }
    };
});

 

ngModelController中有幾個屬性能夠用來檢查甚至修改視圖:

1. $viewValue
$viewValue屬性保存着更新視圖所需的實際字符串。
2. $modelValue
$modelValue由數據模型持有。 $modelValue和$viewValue多是不一樣的,取決於$parser流水線是否對其進行了操做

3. $parsers

$parsers的值是一個由函數組成的數組,其中的函數會以流水線的形式被逐一調用。ngModel從DOM中讀取的值會被傳入$parsers中的函數,並依次被其中的解析器處理
4. $formatters
$formatters的值是一個由函數組成的數組,其中的函數會以流水線的形式在數據模型的值發生變化時被逐一調用。它和$parser流水線互不影響,用來對值進行格式化和轉換,以便在綁定了這個值的控件中顯示。
5. $viewChangeListeners
$viewChangeListeners的值是一個由函數組成的數組,其中的函數會以流水線的形式在視圖中的值發生變化時被逐一調用。經過$viewChangeListeners,能夠在無需使用$watch的狀況下實現相似的行爲。因爲返回值會被忽略,所以這些函數不須要返回值。
6. $error
$error對象中保存着沒有經過驗證的驗證器名稱以及對應的錯誤信息。
7. $pristine
$pristine的值是布爾型的,能夠告訴咱們用戶是否對控件進行了修改。
8. $dirty
$dirty的值和$pristine相反,能夠告訴咱們用戶是否和控件進行過交互。
9. $valid
$valid值能夠告訴咱們當前的控件中是否有錯誤。當有錯誤時值爲false, 沒有錯誤時值爲true。
10. $invalid
$invalid值能夠告訴咱們當前控件中是否存在至少一個錯誤,它的值和$valid相反。

自定義驗證:

angular.module('validationExample', [])
.directive('ensureUnique',function($http) {
    return {
        require: 'ngModel',
        link: function(scope, ele, attrs, c) {
            scope.$watch(attrs.ngModel, function() {
                $http({
                    method: 'POST',
                    url: '/api/check/' + attrs.ensureUnique,
                    data: {field: attrs.ensureUnique, valud:scope.ngModel
                    }).success(function(data,status,headers,cfg) {
                        c.$setValidity('unique', data.isUnique);
                    }).error(function(data,status,headers,cfg) {
                        c.$setValidity('unique', false);
                    });
            });
        }
    };
});  

 

出於演示目的,儘管咱們在指令內置入了一個$http調用,可是在產品中的指令內使用$http是不明智的。相反,將它置入到服務中會更好。關於服務的更多信息請參第14章。


<input type="text"  placeholder="Desired username"  name="username「  ng-model="signup.username"
            ng-minlength="3"  ng-maxlength="20"  ensure-unique="username" required />

每當ngModel中對應的字段發生變化就會向服務器發送請求,以檢查用戶名是不是惟一的

相關文章
相關標籤/搜索