如何使用$ scope。$ watch和$ scope。$在AngularJS中申请?

时间:2013-02-27 12:50:08

标签: angularjs angularjs-scope

我不明白如何使用$scope.$watch$scope.$apply。官方文档没有帮助。

具体我不明白:

  • 他们是否与DOM连接?
  • 如何更新模型的DOM更改?
  • 他们之间的联系点是什么?

我尝试了this tutorial,但理解$watch$apply是理所当然的。

$apply$watch做了什么,以及如何正确使用它们?

7 个答案:

答案 0 :(得分:1709)

您需要了解AngularJS的工作原理才能理解它。

摘要周期和$ scope

首先,AngularJS定义了一个所谓的摘要周期的概念。这个循环可以被认为是一个循环,在此期间AngularJS检查所有$scope的所有变量观察是否有任何变化。因此,如果您在控制器中定义了$scope.myVar并且此变量被标记为正在观看,那么您隐含地告诉AngularJS在每次迭代中监视myVar上的更改。循环。

一个自然的后续问题是:是否有$scope被关注的所有内容?幸运的是,没有。如果您要查看$scope中每个对象的更改,那么很快就会需要一段时间来评估摘要循环,您很快就会遇到性能问题。这就是为什么AngularJS团队给了我们两种方式来声明一些$scope变量被观察(见下文)。

$ watch有助于监听$ scope更改

有两种方法可以将$scope变量声明为正在观看。

  1. 通过表达式<span>{{myVar}}</span>
  2. 在模板中使用它
  3. 通过$watch服务
  4. 手动添加

    广告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的每个观察者,只要没有任何变化。如果在摘要循环中没有发生更多变化,则认为它已完成。

    您可以在Controller中明确地将对象附加到$scope对象,也可以直接在视图中以{{expression}}形式声明它们。

    我希望这有助于澄清有关这一切的一些基本知识。

    进一步阅读:

答案 1 :(得分:157)

在AngularJS中,我们更新模型,我们的视图/模板“自动”更新DOM(通过内置或自定义指令)。

$ apply和$ watch,都是Scope方法,与DOM无关。

Concepts页面(“运行时”部分)对$ digest循环,$ apply,$ evalAsync队列和$ watch列表有很好的解释。以下是文字附带的图片:

$digest loop

无论代码是否有权访问范围 - 通常是控制器和指令(它们的链接函数和/或它们的控制器) - 都可以设置一个“watchExpression”,AngularJS将根据该范围进行评估。只要AngularJS进入其$ digest循环(特别是“$ watch list”循环),就会发生此评估。您可以观看单个范围属性,您可以定义一个函数来一起观看两个属性,您可以观察数组的长度等。

当事情发生在“AngularJS内部”时 - 例如,您键入一个启用了AngularJS双向数据绑定的文本框(即使用ng-model),$ http回调触发等等 - $ apply已被调用,所以我们在上图中的“AngularJS”矩形内。将评估所有watchExpressions(可能不止一次 - 直到没有检测到进一步的更改)。

当事情发生在“AngularJS之外”时 - 例如,你在一个指令中使用了bind()然后该事件触发,导致你的回调被调用,或者一些jQuery注册的回调触发 - 我们仍然在“Native”中长方形。如果回调代码修改了任何$ watch正在观看的内容,请调用$ apply进入AngularJS矩形,导致$ digest循环运行,因此AngularJS会注意到更改并发挥其魔力。

答案 2 :(得分:61)

This blog已经涵盖所有创建示例和可理解的解释。

AngularJS $scope函数$watch(), $digest()$apply()是AngularJS中的一些核心函数。了解AngularJS必须了解$watch()$digest()$apply()

当您从视图中的某个位置创建数据绑定到$ scope对象上的变量时,AngularJS会创建一个&#34; watch&#34;内部。手表意味着AngularJS会监视$scope object上变量的变化。框架是&#34;观看&#34;变量。手表是使用$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() {}
             );

此示例valule函数返回$scope变量scope.data.myVar。如果此变量的值发生更改,则将返回不同的值,AngularJS将调用侦听器函数。

注意value函数如何将范围作为参数(名称中没有$)。通过此参数,value函数可以访问$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()时,所有数据绑定都会更新。

答案 3 :(得分:44)

AngularJS扩展了这个 events-loop ,创建了一个名为AngularJS context的东西。

<强> $手表()

每次在UI中绑定某些内容时,都会在$watch列表中插入 $watch

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

这里我们有$scope.user,它绑定到第一个输入,我们有$scope.pass,它绑定到第二个输入。为此,我们将两个 $watch es添加到$watch列表

当加载我们的模板时,AKA处于链接阶段,编译器将查找每个指令并创建所需的所有$watch

AngularJS提供$watch$watchcollection$watch(true)。下面是一张简洁的图表,解释了从watchers in depth获取的所有三个。

Enter image description here

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. 由于$watch正在关注$ scope.name中的更改 报告更改,它将强制执行另一个$digest循环。
  4. 新循环没有任何报告。
  5. 浏览器获取控制权,它将更新DOM 反映$ scope.name
  6. 的新值
  7. 这里重要的是进入AngularJS上下文的每个事件都将运行$digest循环。这意味着每次我们在输入中写一个字母时,循环都会检查此页面中的每个$watch
  8. $申请()

    如果你在事件被触发时调用$apply,它将通过角度上下文,但如果你不调用它,它将在它之外运行。就是这么简单。 $apply将在内部调用$digest() 循环,它将遍历所有监视以确保使用新更新的值更新DOM。

    $apply()方法会触发整个$scope链上的观察者,而$digest()方法只会触发当前$scope及其children上的观察者。 当高层$scope个对象都不需要了解本地更改时,您可以使用$digest()

答案 4 :(得分:17)

还有$watchGroup$watchCollection。具体来说,$watchGroup非常有用,如果你想调用一个函数来更新一个在一个非dom对象的视图中有多个属性的对象,例如, canvas,webGL或服务器请求中的其他视图。这里是文档link

答案 5 :(得分:17)

我发现了非常深入的视频,其中涵盖了application.html.erb <!DOCTYPE html> <html> <head> <title><%= full_title(yield(:title)) %></title> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> <meta name="viewport" content="width=device-width, initial-scale=1"> <%= render 'layouts/shim' %> </head> <body> <%= render 'layouts/header' %> <div class="container-fluid"> </div> </body> </html> application.js //= require jquery2 //= require jquery_ujs //= require jquery-ui/autocomplete //= require autocomplete-rails //= require dropzone //= require select2-full //= require bootstrap //= require turbolinks //= require_tree . //= require selectize application.css *= require jquery-ui/autocomplete *= require dropzone/dropzone *= require select2 *= require select2-bootstrap *= require selectize *= require selectize.default *= require_tree . *= require_self */ $watch$apply和摘要周期:

以下是在这些视频中使用的几张幻灯片来解释这些概念(以防万一,如果以上链接被删除/无效)。

Enter image description here

在上图中,“$ scope.c”未被监视,因为它未在任何数据绑定中使用(在标记中)。其他两个($digest$scope.a)将会被观看。

Enter image description here

从上图:根据各自的浏览器事件,AngularJS捕获事件,执行摘要周期(通过所有手表进行更改),执行监视功能并更新DOM。如果不是浏览器事件,则可以使用$scope.b$apply手动触发摘要周期。

有关$digest$apply的更多信息:

Enter image description here

答案 6 :(得分:12)

刚读完以上所有内容,无聊而困倦(抱歉,但这是真的)。非常技术性,深入,细致,干燥。 我为什么要写作?因为AngularJS是庞大的,许多相互关联的概念可以让任何人变得疯狂。我经常问自己,我是不是很聪明才能理解它们?没有!这是因为很少有人可以用 for dummie language 来解释所有术语的技术! 好的,让我试试:

1)它们都是事件驱动的东西。(我听到笑声,但请继续阅读)

  

如果你不知道什么是事件驱动的话   想你放一个按钮   在页面上,使用&#34;点击&#34;等待,将其连接到一个功能   用户点击它来触发你在里面种植的动作   功能。或者想想&#34;触发&#34; SQL Server / Oracle。

2)$ watch&#34;点击&#34;。

  

它的特殊之处在于它需要2个函数作为参数,第一个参数   给出事件的值,第二个将值带入   考虑...

3)$ digest是不知疲倦地检查的老板, bla-bla-bla但是一个好老板。

4)$ apply为您提供了手动操作的方式,就像防故障一样(如果点击不启动,则强制它运行) 。)

现在,让我们视觉化。想象一下,这样可以更容易理解这个想法:

在餐厅

- WAITERS 应该接受客户的订单,这是

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

- MANAGER 跑来跑去确保所有服务员都醒着,以应对客户的任何变化迹象。这是$digest()

- OWNER 具有根据要求驱动每个人的最终力量,这是$apply()