angularjs-scope directive教學 - 如何使用$ scope。$ watch和$ scope。$在AngularJS中應用?




controller require (7)

我不明白如何使用$scope.$watch$scope.$apply 。 官方文檔沒有幫助。

具體我不明白:

  • 他們是否連接到DOM?
  • 我如何更新模型的DOM更改?
  • 它們之間的連接點是什麼?

我試過這個教程 ,但它理解$watch$apply是理所當然的。

$apply$watch做什麼,我該如何恰當地使用它們?


Answers

我發現了非常深入的視頻,涵蓋了$watch$apply$digest和digest週期:

以下是這些視頻中使用的幾張幻燈片來解釋這些概念(以防萬一以上鍊接被刪除/不工作)。

在上面的圖片中,“$ scope.c”沒有被監視,因為它沒有用在任何數據綁定中(在標記中)。 另外兩個( $scope.a$scope.b )將被監視。

從上面的圖片可以看出:AngularJS基於各自的瀏覽器事件捕獲事件,執行摘要循環(遍歷所有更改的手錶),執行監視功能並更新DOM。 如果不是瀏覽器事件,摘要循環可以使用$apply$digest手動觸發。

關於$apply$digest更多信息:


這篇博客已經涵蓋了所有的創建示例和可理解的解釋。

AngularJS $scope函數$watch(), $digest()$apply()是AngularJS中的一些中心函數。 為了理解AngularJS,理解$watch()$digest()$apply()是必不可少的。

當您從視圖中的某處創建數據綁定到$ scope對像上的變量時,AngularJS將在內部創建一個“手錶”。 手錶意味著AngularJS監視$scope object對像上變量的變化。 該框架正在“觀察”變量。 手錶是使用$scope.$watch()函數創建的,我將在本文後面介紹。

在應用程序的關鍵點AngularJS調用$scope.$digest()函數。 此函數遍歷所有監視並檢查是否有任何監視的變量已更改。 如果觀察變量已更改,則會調用相應的偵聽器函數。 監聽器函數可以完成它需要做的任何工作,例如更改HTML文本以反映監視變量的新值。 因此, $digest()函數是觸發數據綁定更新的東西。

大部分時間AngularJS都會調用$ scope。$ watch()和$scope.$digest()函數,但在某些情況下,您可能需要自己調用它們。 因此,了解他們的工作方式真的很好。

$scope.$apply()函數用於執行某些代碼,然後調用$scope.$digest() ,以便檢查所有手錶,並調用相應的監聽器函數。 當將AngularJS與其他代碼集成時, $apply()函數非常有用。

我將在本文的其餘部分詳細介紹$watch(), $digest()$apply()函數。

$表()

$scope.watch()函數創建一些變量的監視。 當您註冊手錶時,您將兩個函數作為參數傳遞給$watch()函數:

  • 值函數
  • 偵聽器功能

這裡是一個例子:

$scope.$watch(function() {},
              function() {}
             );

第一個函數是值函數,第二個函數是偵聽器函數。

值函數應該返回正在觀察的值。 然後AngularJS可以檢查返回的值與watch函數上一次返回的值。 這樣AngularJS可以確定這個值是否已經改變。 這裡是一個例子:

$scope.$watch(function(scope) { return scope.data.myVar },
              function() {}
             );

這個示例的值函數返回$scope變量scope.data.myVar 。 如果此變量的值發生變化,將返回一個不同的值,並且AngularJS將調用監聽器函數。

注意值函數如何將範圍作為參數(名稱中沒有$)。 通過這個參數,值函數可以訪問$scope和它的變量。 如果需要,值函數也可以觀察全局變量,但大多數情況下您會觀察$scope變量。

如果值已更改,監聽器函數應該做任何需要做的事情。 也許你需要改變另一個變量的內容,或者設置HTML元素的內容或其他東西。 這裡是一個例子:

$scope.$watch(function(scope) { return scope.data.myVar },
              function(newValue, oldValue) {
                  document.getElementById("").innerHTML =
                      "" + newValue + "";
              }
             );

本示例將HTML元素的內部HTML設置為嵌入到b元素中的變量的新值,該元素使值為粗體。 當然,您可以使用代碼{{ data.myVar }完成此操作,但這只是您可以在偵聽器函數內部執行的一個示例。

$摘要()

$scope.$digest()函數迭代$scope object中的所有監視,以及它的子$ scope對象(如果有的話)。 當$digest()遍歷手錶時,它會調用每個手錶的值函數。 如果value函數返回的值不同於上次調用該函數返回的值,那麼將調用該表的偵聽器函數。

每當AngularJS認為有必要的時候,都會調用$digest()函數。 例如,在執行按鈕點擊處理程序之後,或者在AJAX調用返回後(執行done()/ fail()回調函數之後)。

您可能會遇到一些AngularJS不會為您調用$digest()函數的情況。 通常您會發現數據綁定不會更新顯示的值。 在這種情況下,調用$scope.$digest() ,它應該可以工作。 或者,你可以使用$scope.$apply()來代替,我將在下一節中解釋它。

$適用()

$scope.$apply()函數將一個函數作為參數執行,並在$scope.$digest()被內部調用。 這使您更容易確保所有手錶都已被檢查,從而刷新所有數據綁定。 這是一個$apply()示例:

$scope.$apply(function() {
    $scope.data.myVar = "Another value";
});

傳遞給$apply()函數作為參數的函數將改變$scope.data.myVar的值。 當函數退出時,AngularJS將調用$scope.$digest()函數,以便檢查所有手錶的觀察值的變化。

為了說明$watch()$digest( )和$apply()工作的,請看下面的例子:

<div ng-controller="myController">
    {{data.time}}

    <br/>
    <button ng-click="updateTime()">update time - ng-click</button>
    <button id="updateTimeButton"  >update time</button>
</div>


<script>
    var module       = angular.module("myapp", []);
    var myController1 = module.controller("myController", function($scope) {

        $scope.data = { time : new Date() };

        $scope.updateTime = function() {
            $scope.data.time = new Date();
        }

        document.getElementById("updateTimeButton")
                .addEventListener('click', function() {
            console.log("update time clicked");
            $scope.data.time = new Date();
        });
    });
</script>

他的示例將$scope.data.time變量綁定到一個插值指令,該指令將變量值合併到HTML頁面中。 該綁定在$scope.data.time variable內部創建一個監視。

該示例還包含兩個按鈕。 第一個按鈕有一個ng-click監聽器。 點擊該按鈕後,將調用$scope.updateTime()函數,然後AngularJS調用$scope.$digest()以便更新數據綁定。

第二個按鈕從控制器函數內部獲得一個標準的JavaScript事件監聽器。 當點擊第二個按鈕時,執行偵聽器功能。 正如你所看到的,兩個按鈕的偵聽器函數幾乎完全相同,但是當第二個按鈕的偵聽器函數被調用時,數據綁定不會被更新。 這是因為$scope.$digest()在第二個按鈕的事件偵聽器執行後沒有被調用。 因此,如果您單擊第二個按鈕,則會在$scope.data.time變量中更新時間,但不會顯示新時間。

為了解決這個問題,我們可以添加一個$scope.$digest()調用到按鈕事件監聽器的最後一行,如下所示:

document.getElementById("updateTimeButton")
        .addEventListener('click', function() {
    console.log("update time clicked");
    $scope.data.time = new Date();
    $scope.$digest();
});

而不是在按鈕偵聽器函數內部調用$digest() ,也可以像這樣使用$apply()函數:

document.getElementById("updateTimeButton")
        .addEventListener('click', function() {
    $scope.$apply(function() {
        console.log("update time clicked");
        $scope.data.time = new Date();
    });
});

請注意, $scope.$apply()函數是如何從按鈕事件偵聽器中調用的,以及$scope.data.time變量的更新如何在作為參數傳遞給$apply()函數的函數中執行。 當$apply()函數調用完成時,AngularJS會在內部調用$digest() ,以便更新所有數據綁定。


AngularJS擴展了這個事件循環 ,創建了一個叫AngularJS context

$表()

每次你在UI中綁定一些東西時,你都會$watch列表中插入$watch

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

這裡我們有$scope.user ,它綁定到第一個輸入,我們有$scope.pass ,它綁定到第二個。 這樣做,我們將兩個$watch添加$watch列表中

當我們的模板被加載時,AKA在鏈接階段,編譯器會查找每個指令並創建所需的所有$watch

AngularJS提供$watch$watchcollection$watch(true) 。 下面是一個整潔的圖解釋所有三個從觀察者深入採取。

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

http://jsfiddle.net/2Lyn0Lkb/

$digest循環

當瀏覽器收到一個可以由AngularJS上下文管理的事件時, $digest循環將被觸發。 該循環由兩個較小的循環組成。 一個處理$evalAsync隊列,另一個處理$watch list$digest將遍歷我們擁有的$watch列表

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

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

這裡我們只有一個$watch因為ng-click不會創建任何手錶。

我們按下按鈕。

  1. 瀏覽器收到一個將進入AngularJS上下文的事件
  2. $digest循環將運行,並會詢問每個$ watch的更改。
  3. 由於正在監視$ scope.name中的變化的$ watch報告了一個變化,它將強制另一個$digest循環。
  4. 新的循環不報告。
  5. 瀏覽器獲取控制權,它將更新反映$ scope.name的新值的DOM
  6. 這裡最重要的是進入AngularJS上下文的EVERY事件將運行一個$digest循環。 這意味著每次我們在輸入中寫入一個字母時,循環都會檢查該頁面中的每個$watch

$適用()

如果在觸發事件時調用$apply ,它將通過角上下文,但如果不調用它,它將在其外運行。 就是這麼簡單。 $apply將在內部調用$digest()循環,並且它將迭代所有手錶以確保DOM更新為新更新的值。

$apply()方法將觸發整個$scope鏈中的觀察者,而$digest()方法只會觸發當前$scope及其children觀察者。 當沒有一個較高級的$scope對象需要知道本地更改時,可以使用$digest()


剛剛讀完上述所有內容,無聊和困倦(抱歉,但是確實如此)。 非常技術性,深入,細緻,乾燥。 我為什麼要寫作? 因為AngularJS非常龐大,所以很多相互連接的概念可以讓任何人都瘋了。 我經常問自己,我不夠聰明來理解他們嗎? 沒有! 這是因為很少有人能夠用所有術語來解釋這種技術。 好的,讓我試試看:

1)他們都是事件驅動的東西。 (我聽到笑聲,但繼續讀下去)

如果你不知道事件驅動是什麼然後認為你在頁面上放置一個按鈕,使用“點擊”功能將它掛起來,等待用戶點擊它以觸發你在功能。 或者想想SQL Server / Oracle的“觸發器”。

2)$ watch是“點擊”。

有什麼特別的地方是它需要2個函數作為參數,第一個函數給出事件的值,第二個函數將這個值考慮在內......

3)美元消化是老闆,不知疲倦地檢查 ,bla-bla-bla,但是一個好老闆。

4)$ apply可以為你提供手動操作的方式 ,比如防止失敗(如果點擊沒有啟動,你強制它運行。)

現在,讓我們將其視覺化。 想像一下,讓它更容易抓住這個想法:

在一家餐館,

- 等待者應該接受客戶的訂單,這是

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

- 經理人四處奔走,以確保所有服務員都清醒過來,對任何客戶變化的跡像作出響應。 這是$digest()

-擁有者可以根據要求驅動每個人,這是$apply()


您需要了解AngularJS如何工作才能理解它。

摘要周期和$範圍

首先,AngularJS定義了一個所謂摘要循環的概念。 這個循環可以被認為是一個循環,在這個循環中AngularJS檢查所有$scope 監視的所有變量是否有變化。 因此,如果您的控制器中定義了$scope.myVar並且該變量被標記為被監視 ,那麼您隱式地告訴AngularJS在循環的每次迭代中監視myVar上的更改。

一個自然的後續問題將是:是否所有附加到$scope被監視? 幸運的是,沒有。 如果您要監視$scope每個對象的更改,那麼快速摘要循環需要很長時間才能評估,並且您很快就會遇到性能問題。 這就是為什麼AngularJS團隊給了我們兩種方法來聲明一些$scope變量被監視(閱讀下面)。

$ watch有助於監聽$ scope變化

有兩種方法可以將$scope變量聲明為被監視。

  1. 通過表達式<span>{{myVar}}</span>在模板中使用它
  2. 通過$watch服務手動添加它

廣告1)這是最常見的情況,我相信你以前也見過它,但你不知道這是在後台創建的。 是的,它已經! 使用AngularJS指令(如ng-repeat )也可以創建隱式手錶。

廣告2)這是你如何創建自己的手錶$watch服務可以幫助您在附加到$scope某個值發生更改時運行一些代碼。 它很少使用,但有時是有幫助的。 例如,如果您想在每次'myVar'更改時運行一些代碼,則可以執行以下操作:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$ apply允許將更改與摘要循環集成

您可以像集成機制那樣考慮$apply函數 。 您會發現,每次您直接更改附加到$scope對象的某個觀察變量時 ,AngularJS都會知道發生了變化。 這是因為AngularJS已經知道要監視這些變化。 因此,如果發生在框架管理的代碼中,摘要循環將繼續。

但是,有時候您想要改變AngularJS世界以外的某些值,並看到這些更改正常傳播。 考慮一下 - 你有一個$scope.myVar值,它將在jQuery的$.ajax()處理程序中被修改。 這將在未來某個時候發生。 AngularJS不能等待這個事情發生,因為它沒有被指示等待jQuery。

為了解決這個問題,已經引入了$apply 。 它可以讓你明確地開始消化循環。 不過,你應該只使用它來將一些數據遷移到AngularJS(與其他框架集成),但從來沒有使用這個方法結合常規的AngularJS代碼,因為AngularJS會拋出一個錯誤。

這與DOM有什麼關係?

那麼,你應該再次遵循教程,現在你知道這一切。 摘要循環將確保UI和JavaScript代碼保持同步,只要沒有任何變化,就評估附加到所有$scope的每個觀察者。 如果在摘要循環中沒有發生更多變化,則認為它已完成。

您可以直接在視圖中將對象附加到$scope對象,或者直接在{{expression}}表單中聲明它們。

我希望這有助於澄清關於這一切的一些基本知識。

更多閱讀材料:


還有$watchGroup$watchCollection 。 特別是,如果你想調用一個函數來更新一個視圖中具有多個屬性的對象,而不是dom對象,例如canvas,webGL或服務器請求中的其他視圖, $watchGroup真的很有用。 這裡是文檔link


可以在指令中添加三種範圍的方法:

  1. 父範圍 :這是默認範圍繼承。

該指令及其父(其所在的控制器/指令)範圍相同。 因此,對指令中的作用域變量所做的任何更改都會反映在父控制器中。 您不需要指定它,因為它是默認值。

  1. 如果將指令的作用域變量指定為true,則子作用域 :指令將創建一個從父作用域繼承的子作用域。

在這裡,如果更改指令中的範圍變量,它不會反映在父範圍中,但是如果更改了範圍變量的屬性,這反映在父範圍中,因為您實際修改了父範圍的範圍變量。

例,

app.directive("myDirective", function(){

    return {
        restrict: "EA",
        scope: true,
        link: function(element, scope, attrs){
            scope.somvar = "new value"; //doesnot reflect in the parent scope
            scope.someObj.someProp = "new value"; //reflects as someObj is of parent, we modified that but did not override.
        }
    };
});
  1. 隔離範圍 :當您想要創建不從控制器範圍繼承的範圍時使用此範圍。

當您創建插件時會發生這種情況,因為這會使指令具有通用性,因為它可以放置在任何HTML中,並且不會受其父範圍的影響。

現在,如果您不想與父範圍進行任何交互,那麼您可以將範圍指定為空對象。 喜歡,

scope: {} //this does not interact with the parent scope in any way

大多數情況並非如此,因為我們需要與父範圍進行一些交互,所以我們希望一些值/更改能夠通過。 出於這個原因,我們使用:

1. "@"   (  Text binding / one-way binding )
2. "="   ( Direct model binding / two-way binding )
3. "&"   ( Behaviour binding / Method binding  )

@表示來自控制器作用域的更改將反映在指令作用域中,但如果修改了指令作用域中的值,則控制器作用域變量不會受到影響。

@總是期望映射的屬性是一個表達式。 這個非常重要; 因為要使“@”前綴有效,我們需要將屬性值封裝在{{}}中。

=是雙向的,所以如果你改變指令作用域中的變量,控制器作用域變量也會受到影響

用於綁定控制器作用域方法,以便在需要時可以從指令中調用它

這裡的優點是變量的名稱在控制器範圍和指令範圍中不需要相同。

例如,指令作用域有一個變量“dirVar”,它與控制器作用域的變量“contVar”同步。 由於一個控制器可以與變量v1同步,而另一個使用相同指令的控制器可以要求dirVar與變量v2同步,所以這賦予了指令很大的權力和泛化。

以下是使用示例:

指令和控制器是:

 var app = angular.module("app", []);
 app.controller("MainCtrl", function( $scope ){
    $scope.name = "Harry";
    $scope.color = "#333333";
    $scope.reverseName = function(){
     $scope.name = $scope.name.split("").reverse().join("");
    };
    $scope.randomColor = function(){
        $scope.color = '#'+Math.floor(Math.random()*16777215).toString(16);
    };
});
app.directive("myDirective", function(){
    return {
        restrict: "EA",
        scope: {
            name: "@",
            color: "=",
            reverse: "&"
        },
        link: function(element, scope, attrs){
           //do something like
           $scope.reverse(); 
          //calling the controllers function
        }
    };
});

和html(注意@和=的差別):

<div my-directive
  class="directive"
  name="{{name}}"
  reverse="reverseName()"
  color="color" >
</div>

這是一個link到博客,很好地描述它。







angularjs angularjs-scope